// File: crates/router/src/compatibility/stripe/refunds.rs

pub mod types;

use actix_web::{web, HttpRequest, HttpResponse};
use error_stack::report;
use router_env::{instrument, tracing, Flow, Tag};

use crate::{
    compatibility::{stripe::errors, wrap},
    core::{api_locking, refunds},
    logger, routes,
    services::{api, authentication as auth},
    types::api::refunds as refund_types,
};

#[instrument(skip_all, fields(flow = ?Flow::RefundsCreate, payment_id))]
pub async fn refund_create(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
) -> HttpResponse {
    let payload: types::StripeCreateRefundRequest = match qs_config
        .deserialize_bytes(&form_payload)
        .map_err(|err| report!(errors::StripeErrorCode::from(err)))
    {
        Ok(p) => p,
        Err(err) => return api::log_and_return_error_response(err),
    };

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

    logger::info!(tag = ?Tag::CompatibilityLayerRequest, payload = ?payload);

    let create_refund_req: refund_types::RefundRequest = payload.into();

    let flow = Flow::RefundsCreate;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripeRefundResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        create_refund_req,
        |state, auth: auth::AuthenticationData, req, _| {
            let platform = auth.into();
            refunds::refund_create_core(state, platform, None, req)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[instrument(skip_all, fields(flow))]
pub async fn refund_retrieve_with_gateway_creds(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
) -> HttpResponse {
    let refund_request: refund_types::RefundsRetrieveRequest = match qs_config
        .deserialize_bytes(&form_payload)
        .map_err(|err| report!(errors::StripeErrorCode::from(err)))
    {
        Ok(payload) => payload,
        Err(err) => return api::log_and_return_error_response(err),
    };

    let flow = match refund_request.force_sync {
        Some(true) => Flow::RefundsRetrieveForceSync,
        _ => Flow::RefundsRetrieve,
    };

    tracing::Span::current().record("flow", flow.to_string());

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripeRefundResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        refund_request,
        |state, auth: auth::AuthenticationData, refund_request, _| {
            let platform = auth.into();
            refunds::refund_response_wrapper(
                state,
                platform,
                None,
                refund_request,
                refunds::refund_retrieve_core_with_refund_id,
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[instrument(skip_all, fields(flow = ?Flow::RefundsRetrieveForceSync))]
pub async fn refund_retrieve(
    state: web::Data<routes::AppState>,
    req: HttpRequest,
    path: web::Path<String>,
) -> HttpResponse {
    let refund_request = refund_types::RefundsRetrieveRequest {
        refund_id: path.into_inner(),
        force_sync: Some(true),
        merchant_connector_details: None,
        all_keys_required: None,
    };

    let flow = Flow::RefundsRetrieveForceSync;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripeRefundResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        refund_request,
        |state, auth: auth::AuthenticationData, refund_request, _| {
            let platform = auth.into();
            refunds::refund_response_wrapper(
                state,
                platform,
                None,
                refund_request,
                refunds::refund_retrieve_core_with_refund_id,
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[instrument(skip_all, fields(flow = ?Flow::RefundsUpdate))]
pub async fn refund_update(
    state: web::Data<routes::AppState>,
    req: HttpRequest,
    path: web::Path<String>,
    form_payload: web::Form<types::StripeUpdateRefundRequest>,
) -> HttpResponse {
    let mut payload = form_payload.into_inner();
    payload.refund_id = path.into_inner();
    let create_refund_update_req: refund_types::RefundUpdateRequest = payload.into();
    let flow = Flow::RefundsUpdate;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripeRefundResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        create_refund_update_req,
        |state, auth: auth::AuthenticationData, req, _| {
            let platform = auth.into();
            refunds::refund_update_core(state, platform, req)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


// File: crates/router/src/compatibility/stripe/webhooks.rs

#[cfg(feature = "payouts")]
use api_models::payouts as payout_models;
use api_models::{
    enums::{Currency, DisputeStatus, MandateStatus},
    webhooks::{self as api},
};
use common_utils::{crypto::SignMessage, date_time, ext_traits::Encode};
#[cfg(feature = "payouts")]
use common_utils::{
    id_type,
    pii::{self, Email},
};
use error_stack::ResultExt;
use router_env::logger;
use serde::Serialize;

use super::{
    payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse,
};
use crate::{
    core::{
        errors,
        webhooks::types::{OutgoingWebhookPayloadWithSignature, OutgoingWebhookType},
    },
    headers,
    services::request::Maskable,
};

#[derive(Serialize, Debug)]
pub struct StripeOutgoingWebhook {
    id: String,
    #[serde(rename = "type")]
    stype: &'static str,
    object: &'static str,
    data: StripeWebhookObject,
    created: u64,
    // api_version: "2019-11-05", // not used
}

impl OutgoingWebhookType for StripeOutgoingWebhook {
    fn get_outgoing_webhooks_signature(
        &self,
        payment_response_hash_key: Option<impl AsRef<[u8]>>,
    ) -> errors::CustomResult<OutgoingWebhookPayloadWithSignature, errors::WebhooksFlowError> {
        let timestamp = self.created;

        let payment_response_hash_key = payment_response_hash_key
            .ok_or(errors::WebhooksFlowError::MerchantConfigNotFound)
            .attach_printable("For stripe compatibility payment_response_hash_key is mandatory")?;

        let webhook_signature_payload = self
            .encode_to_string_of_json()
            .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
            .attach_printable("failed encoding outgoing webhook payload")?;

        let new_signature_payload = format!("{timestamp}.{webhook_signature_payload}");
        let v1 = hex::encode(
            common_utils::crypto::HmacSha256::sign_message(
                &common_utils::crypto::HmacSha256,
                payment_response_hash_key.as_ref(),
                new_signature_payload.as_bytes(),
            )
            .change_context(errors::WebhooksFlowError::OutgoingWebhookSigningFailed)
            .attach_printable("Failed to sign the message")?,
        );

        let t = timestamp;
        let signature = Some(format!("t={t},v1={v1}"));

        Ok(OutgoingWebhookPayloadWithSignature {
            payload: webhook_signature_payload.into(),
            signature,
        })
    }

    fn add_webhook_header(header: &mut Vec<(String, Maskable<String>)>, signature: String) {
        header.push((
            headers::STRIPE_COMPATIBLE_WEBHOOK_SIGNATURE.to_string(),
            signature.into(),
        ))
    }
}

#[derive(Serialize, Debug)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
pub enum StripeWebhookObject {
    PaymentIntent(Box<StripePaymentIntentResponse>),
    Refund(StripeRefundResponse),
    Dispute(StripeDisputeResponse),
    Mandate(StripeMandateResponse),
    #[cfg(feature = "payouts")]
    Payout(StripePayoutResponse),
    Subscriptions,
}

#[derive(Serialize, Debug)]
pub struct StripeDisputeResponse {
    pub id: String,
    pub amount: String,
    pub currency: Currency,
    pub payment_intent: id_type::PaymentId,
    pub reason: Option<String>,
    pub status: StripeDisputeStatus,
}

#[derive(Serialize, Debug)]
pub struct StripeMandateResponse {
    pub mandate_id: String,
    pub status: StripeMandateStatus,
    pub payment_method_id: String,
    pub payment_method: String,
}

#[cfg(feature = "payouts")]
#[derive(Clone, Serialize, Debug)]
pub struct StripePayoutResponse {
    pub id: id_type::PayoutId,
    pub amount: i64,
    pub currency: String,
    pub payout_type: Option<common_enums::PayoutType>,
    pub status: StripePayoutStatus,
    pub name: Option<masking::Secret<String>>,
    pub email: Option<Email>,
    pub phone: Option<masking::Secret<String>>,
    pub phone_country_code: Option<String>,
    pub created: Option<i64>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub entity_type: common_enums::PayoutEntityType,
    pub recurring: bool,
    pub error_message: Option<String>,
    pub error_code: Option<String>,
}

#[cfg(feature = "payouts")]
#[derive(Clone, Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripePayoutStatus {
    PayoutSuccess,
    PayoutFailure,
    PayoutProcessing,
    PayoutCancelled,
    PayoutInitiated,
    PayoutExpired,
    PayoutReversed,
}

#[cfg(feature = "payouts")]
impl From<common_enums::PayoutStatus> for StripePayoutStatus {
    fn from(status: common_enums::PayoutStatus) -> Self {
        match status {
            common_enums::PayoutStatus::Success => Self::PayoutSuccess,
            common_enums::PayoutStatus::Failed => Self::PayoutFailure,
            common_enums::PayoutStatus::Cancelled => Self::PayoutCancelled,
            common_enums::PayoutStatus::Initiated => Self::PayoutInitiated,
            common_enums::PayoutStatus::Expired => Self::PayoutExpired,
            common_enums::PayoutStatus::Reversed => Self::PayoutReversed,
            common_enums::PayoutStatus::Pending
            | common_enums::PayoutStatus::Ineligible
            | common_enums::PayoutStatus::RequiresCreation
            | common_enums::PayoutStatus::RequiresFulfillment
            | common_enums::PayoutStatus::RequiresPayoutMethodData
            | common_enums::PayoutStatus::RequiresVendorAccountCreation
            | common_enums::PayoutStatus::RequiresConfirmation => Self::PayoutProcessing,
        }
    }
}

#[cfg(feature = "payouts")]
impl From<payout_models::PayoutCreateResponse> for StripePayoutResponse {
    fn from(res: payout_models::PayoutCreateResponse) -> Self {
        let (name, email, phone, phone_country_code) = match res.customer {
            Some(customer) => (
                customer.name,
                customer.email,
                customer.phone,
                customer.phone_country_code,
            ),
            None => (None, None, None, None),
        };
        Self {
            id: res.payout_id,
            amount: res.amount.get_amount_as_i64(),
            currency: res.currency.to_string(),
            payout_type: res.payout_type,
            status: StripePayoutStatus::from(res.status),
            name,
            email,
            phone,
            phone_country_code,
            created: res.created.map(|t| t.assume_utc().unix_timestamp()),
            metadata: res.metadata,
            entity_type: res.entity_type,
            recurring: res.recurring,
            error_message: res.error_message,
            error_code: res.error_code,
        }
    }
}

#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripeMandateStatus {
    Active,
    Inactive,
    Pending,
}

#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripeDisputeStatus {
    WarningNeedsResponse,
    WarningUnderReview,
    WarningClosed,
    NeedsResponse,
    UnderReview,
    ChargeRefunded,
    Won,
    Lost,
}

impl From<api_models::disputes::DisputeResponse> for StripeDisputeResponse {
    fn from(res: api_models::disputes::DisputeResponse) -> Self {
        Self {
            id: res.dispute_id,
            amount: res.amount.to_string(),
            currency: res.currency,
            payment_intent: res.payment_id,
            reason: res.connector_reason,
            status: StripeDisputeStatus::from(res.dispute_status),
        }
    }
}

impl From<api_models::mandates::MandateResponse> for StripeMandateResponse {
    fn from(res: api_models::mandates::MandateResponse) -> Self {
        Self {
            mandate_id: res.mandate_id,
            payment_method: res.payment_method,
            payment_method_id: res.payment_method_id,
            status: StripeMandateStatus::from(res.status),
        }
    }
}

impl From<MandateStatus> for StripeMandateStatus {
    fn from(status: MandateStatus) -> Self {
        match status {
            MandateStatus::Active => Self::Active,
            MandateStatus::Inactive | MandateStatus::Revoked => Self::Inactive,
            MandateStatus::Pending => Self::Pending,
        }
    }
}

impl From<DisputeStatus> for StripeDisputeStatus {
    fn from(status: DisputeStatus) -> Self {
        match status {
            DisputeStatus::DisputeOpened => Self::WarningNeedsResponse,
            DisputeStatus::DisputeExpired => Self::Lost,
            DisputeStatus::DisputeAccepted => Self::Lost,
            DisputeStatus::DisputeCancelled => Self::WarningClosed,
            DisputeStatus::DisputeChallenged => Self::WarningUnderReview,
            DisputeStatus::DisputeWon => Self::Won,
            DisputeStatus::DisputeLost => Self::Lost,
        }
    }
}

fn get_stripe_event_type(event_type: api_models::enums::EventType) -> &'static str {
    match event_type {
        api_models::enums::EventType::PaymentSucceeded => "payment_intent.succeeded",
        api_models::enums::EventType::PaymentFailed => "payment_intent.payment_failed",
        api_models::enums::EventType::PaymentProcessing
        | api_models::enums::EventType::PaymentPartiallyAuthorized => "payment_intent.processing",
        api_models::enums::EventType::PaymentCancelled
        | api_models::enums::EventType::PaymentCancelledPostCapture
        | api_models::enums::EventType::PaymentExpired => "payment_intent.canceled",

        // the below are not really stripe compatible because stripe doesn't provide this
        api_models::enums::EventType::ActionRequired => "action.required",
        api_models::enums::EventType::RefundSucceeded => "refund.succeeded",
        api_models::enums::EventType::RefundFailed => "refund.failed",
        api_models::enums::EventType::DisputeOpened => "dispute.failed",
        api_models::enums::EventType::DisputeExpired => "dispute.expired",
        api_models::enums::EventType::DisputeAccepted => "dispute.accepted",
        api_models::enums::EventType::DisputeCancelled => "dispute.cancelled",
        api_models::enums::EventType::DisputeChallenged => "dispute.challenged",
        api_models::enums::EventType::DisputeWon => "dispute.won",
        api_models::enums::EventType::DisputeLost => "dispute.lost",
        api_models::enums::EventType::MandateActive => "mandate.active",
        api_models::enums::EventType::MandateRevoked => "mandate.revoked",

        // as per this doc https://stripe.com/docs/api/events/types#event_types-payment_intent.amount_capturable_updated
        api_models::enums::EventType::PaymentAuthorized => {
            "payment_intent.amount_capturable_updated"
        }
        // stripe treats partially captured payments as succeeded.
        api_models::enums::EventType::PaymentCaptured => "payment_intent.succeeded",
        api_models::enums::EventType::PayoutSuccess => "payout.paid",
        api_models::enums::EventType::PayoutFailed => "payout.failed",
        api_models::enums::EventType::PayoutInitiated => "payout.created",
        api_models::enums::EventType::PayoutCancelled => "payout.canceled",
        api_models::enums::EventType::PayoutProcessing => "payout.created",
        api_models::enums::EventType::PayoutExpired => "payout.failed",
        api_models::enums::EventType::PayoutReversed => "payout.reconciliation_completed",
        api_models::enums::EventType::InvoicePaid => "invoice.paid",
    }
}

impl From<api::OutgoingWebhook> for StripeOutgoingWebhook {
    fn from(value: api::OutgoingWebhook) -> Self {
        Self {
            id: value.event_id,
            stype: get_stripe_event_type(value.event_type),
            data: StripeWebhookObject::from(value.content),
            object: "event",
            // put this conversion it into a function
            created: u64::try_from(value.timestamp.assume_utc().unix_timestamp()).unwrap_or_else(
                |error| {
                    logger::error!(
                        %error,
                        "incorrect value for `webhook.timestamp` provided {}", value.timestamp
                    );
                    // Current timestamp converted to Unix timestamp should have a positive value
                    // for many years to come
                    u64::try_from(date_time::now().assume_utc().unix_timestamp())
                        .unwrap_or_default()
                },
            ),
        }
    }
}

impl From<api::OutgoingWebhookContent> for StripeWebhookObject {
    fn from(value: api::OutgoingWebhookContent) -> Self {
        match value {
            api::OutgoingWebhookContent::PaymentDetails(payment) => {
                Self::PaymentIntent(Box::new((*payment).into()))
            }
            api::OutgoingWebhookContent::RefundDetails(refund) => Self::Refund((*refund).into()),
            api::OutgoingWebhookContent::DisputeDetails(dispute) => {
                Self::Dispute((*dispute).into())
            }
            api::OutgoingWebhookContent::MandateDetails(mandate) => {
                Self::Mandate((*mandate).into())
            }
            #[cfg(feature = "payouts")]
            api::OutgoingWebhookContent::PayoutDetails(payout) => Self::Payout((*payout).into()),
            api_models::webhooks::OutgoingWebhookContent::SubscriptionDetails(_) => {
                Self::Subscriptions
            }
        }
    }
}


// File: crates/router/src/compatibility/stripe/errors.rs

use common_utils::{errors::ErrorSwitch, id_type};
use hyperswitch_domain_models::errors::api_error_response as errors;

use crate::core::errors::CustomersErrorResponse;

#[derive(Debug, router_derive::ApiError, Clone)]
#[error(error_type_enum = StripeErrorType)]
pub enum StripeErrorCode {
    /*
    "error": {
        "message": "Invalid API Key provided: sk_jkjgs****nlgs",
        "type": "invalid_request_error"
    }
    */
    #[error(
        error_type = StripeErrorType::InvalidRequestError, code = "IR_01",
        message = "Invalid API Key provided"
    )]
    Unauthorized,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_02", message = "Unrecognized request URL.")]
    InvalidRequestUrl,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "parameter_missing", message = "Missing required param: {field_name}.")]
    ParameterMissing { field_name: String, param: String },

    #[error(
        error_type = StripeErrorType::InvalidRequestError, code = "parameter_unknown",
        message = "{field_name} contains invalid data. Expected format is {expected_format}."
    )]
    ParameterUnknown {
        field_name: String,
        expected_format: String,
    },

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_06",  message = "The refund amount exceeds the amount captured.")]
    RefundAmountExceedsPaymentAmount { param: String },

    #[error(error_type = StripeErrorType::ApiError, code = "payment_intent_authentication_failure", message = "Payment failed while processing with connector. Retry payment.")]
    PaymentIntentAuthenticationFailure { data: Option<serde_json::Value> },

    #[error(error_type = StripeErrorType::ApiError, code = "payment_intent_payment_attempt_failed", message = "Capture attempt failed while processing with connector.")]
    PaymentIntentPaymentAttemptFailed { data: Option<serde_json::Value> },

    #[error(error_type = StripeErrorType::ApiError, code = "dispute_failure", message = "Dispute failed while processing with connector. Retry operation.")]
    DisputeFailed { data: Option<serde_json::Value> },

    #[error(error_type = StripeErrorType::CardError, code = "expired_card", message = "Card Expired. Please use another card")]
    ExpiredCard,

    #[error(error_type = StripeErrorType::CardError, code = "invalid_card_type", message = "Card data is invalid")]
    InvalidCardType,

    #[error(
        error_type = StripeErrorType::ConnectorError, code = "invalid_wallet_token",
        message = "Invalid {wallet_name} wallet token"
    )]
    InvalidWalletToken { wallet_name: String },

    #[error(error_type = StripeErrorType::ApiError, code = "refund_failed", message = "refund has failed")]
    RefundFailed, // stripe error code

    #[error(error_type = StripeErrorType::ApiError, code = "payout_failed", message = "payout has failed")]
    PayoutFailed,

    #[error(error_type = StripeErrorType::ApiError, code = "external_vault_failed", message = "external vault has failed")]
    ExternalVaultFailed,

    #[error(error_type = StripeErrorType::ApiError, code = "internal_server_error", message = "Server is down")]
    InternalServerError,

    #[error(error_type = StripeErrorType::ApiError, code = "internal_server_error", message = "Server is down")]
    DuplicateRefundRequest,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "active_mandate", message = "Customer has active mandate")]
    MandateActive,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "customer_redacted", message = "Customer has redacted")]
    CustomerRedacted,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "customer_already_exists", message = "Customer with the given customer_id already exists")]
    DuplicateCustomer,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such refund")]
    RefundNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "client_secret_invalid", message = "Expected client secret to be included in the request")]
    ClientSecretNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such customer")]
    CustomerNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such config")]
    ConfigNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "duplicate_resource", message = "Duplicate config")]
    DuplicateConfig,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such payment")]
    PaymentNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such payment method")]
    PaymentMethodNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "{message}")]
    GenericNotFoundError { message: String },

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "duplicate_resource", message = "{message}")]
    GenericDuplicateError { message: String },

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such merchant account")]
    MerchantAccountNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such resource ID")]
    ResourceIdNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "Merchant connector account does not exist in our records")]
    MerchantConnectorAccountNotFound { id: String },

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "invalid_request", message = "The merchant connector account is disabled")]
    MerchantConnectorAccountDisabled,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such mandate")]
    MandateNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such API key")]
    ApiKeyNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such payout")]
    PayoutNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such event")]
    EventNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "Duplicate payout request")]
    DuplicatePayout { payout_id: id_type::PayoutId },

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "parameter_missing", message = "Return url is not available")]
    ReturnUrlUnavailable,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate merchant account")]
    DuplicateMerchantAccount,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_label}' already exists in our records")]
    DuplicateMerchantConnectorAccount {
        profile_id: String,
        connector_label: String,
    },

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate payment method")]
    DuplicatePaymentMethod,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "" , message = "deserialization failed: {error_message}")]
    SerdeQsError {
        error_message: String,
        param: Option<String>,
    },

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "payment_intent_invalid_parameter" , message = "The client_secret provided does not match the client_secret associated with the PaymentIntent.")]
    PaymentIntentInvalidParameter { param: String },

    #[error(
        error_type = StripeErrorType::InvalidRequestError, code = "IR_05",
        message = "{message}"
    )]
    InvalidRequestData { message: String },

    #[error(
        error_type = StripeErrorType::InvalidRequestError, code = "IR_10",
        message = "{message}"
    )]
    PreconditionFailed { message: String },

    #[error(
        error_type = StripeErrorType::InvalidRequestError, code = "",
        message = "The payment has not succeeded yet"
    )]
    PaymentFailed,

    #[error(
        error_type = StripeErrorType::InvalidRequestError, code = "",
        message = "The verification did not succeeded"
    )]
    VerificationFailed { data: Option<serde_json::Value> },

    #[error(
        error_type = StripeErrorType::InvalidRequestError, code = "",
        message = "Reached maximum refund attempts"
    )]
    MaximumRefundCount,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Duplicate mandate request. Mandate already attempted with the Mandate ID.")]
    DuplicateMandate,

    #[error(error_type= StripeErrorType::InvalidRequestError, code = "", message = "Successful payment not found for the given payment id")]
    SuccessfulPaymentNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Address does not exist in our records.")]
    AddressNotFound,

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "This PaymentIntent could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}.")]
    PaymentIntentUnexpectedState {
        current_flow: String,
        field_name: String,
        current_value: String,
        states: String,
    },
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "The mandate information is invalid. {message}")]
    PaymentIntentMandateInvalid { message: String },

    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "The payment with the specified payment_id already exists in our records.")]
    DuplicatePayment { payment_id: id_type::PaymentId },

    #[error(error_type = StripeErrorType::ConnectorError, code = "", message = "{code}: {message}")]
    ExternalConnectorError {
        code: String,
        message: String,
        connector: String,
        status_code: u16,
    },

    #[error(error_type = StripeErrorType::CardError, code = "", message = "{code}: {message}")]
    PaymentBlockedError {
        code: u16,
        message: String,
        status: String,
        reason: String,
    },

    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "The connector provided in the request is incorrect or not available")]
    IncorrectConnectorNameGiven,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "No such {object}: '{id}'")]
    ResourceMissing { object: String, id: String },
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "File validation failed")]
    FileValidationFailed,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "File not found in the request")]
    MissingFile,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "File puropse not found in the request")]
    MissingFilePurpose,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "File content type not found")]
    MissingFileContentType,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Dispute id not found in the request")]
    MissingDisputeId,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "File does not exists in our records")]
    FileNotFound,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "File not available")]
    FileNotAvailable,
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Not Supported because provider is not Router")]
    FileProviderNotSupported,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "There was an issue with processing webhooks")]
    WebhookProcessingError,
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "payment_method_unactivated", message = "The operation cannot be performed as the payment method used has not been activated. Activate the payment method in the Dashboard, then try again.")]
    PaymentMethodUnactivated,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "{message}")]
    HyperswitchUnprocessableEntity { message: String },
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "{message}")]
    CurrencyNotSupported { message: String },
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Payment Link does not exist in our records")]
    PaymentLinkNotFound,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Resource Busy. Please try again later")]
    LockTimeout,
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Merchant connector account is configured with invalid {config}")]
    InvalidConnectorConfiguration { config: String },
    #[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert currency to minor unit")]
    CurrencyConversionFailed,
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")]
    PaymentMethodDeleteFailed,
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Extended card info does not exist")]
    ExtendedCardInfoNotFound,
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "not_configured", message = "{message}")]
    LinkConfigurationError { message: String },
    #[error(error_type = StripeErrorType::ConnectorError, code = "CE", message = "{reason} as data mismatched for {field_names}")]
    IntegrityCheckFailed {
        reason: String,
        field_names: String,
        connector_transaction_id: Option<String>,
    },
    #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_28", message = "Invalid tenant")]
    InvalidTenant,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert amount to {amount_type} type")]
    AmountConversionFailed { amount_type: &'static str },
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Platform Bad Request")]
    PlatformBadRequest,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Platform Unauthorized Request")]
    PlatformUnauthorizedRequest,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Profile Acquirer not found")]
    ProfileAcquirerNotFound,
    #[error(error_type = StripeErrorType::HyperswitchError, code = "Subscription Error", message = "Subscription operation: {operation} failed with connector")]
    SubscriptionError { operation: String },
    // [#216]: https://github.com/juspay/hyperswitch/issues/216
    // Implement the remaining stripe error codes

    /*
        AccountCountryInvalidAddress,
        AccountErrorCountryChangeRequiresAdditionalSteps,
        AccountInformationMismatch,
        AccountInvalid,
        AccountNumberInvalid,
        AcssDebitSessionIncomplete,
        AlipayUpgradeRequired,
        AmountTooLarge,
        AmountTooSmall,
        ApiKeyExpired,
        AuthenticationRequired,
        BalanceInsufficient,
        BankAccountBadRoutingNumbers,
        BankAccountDeclined,
        BankAccountExists,
        BankAccountUnusable,
        BankAccountUnverified,
        BankAccountVerificationFailed,
        BillingInvalidMandate,
        BitcoinUpgradeRequired,
        CardDeclineRateLimitExceeded,
        CardDeclined,
        CardholderPhoneNumberRequired,
        ChargeAlreadyCaptured,
        ChargeAlreadyRefunded,
        ChargeDisputed,
        ChargeExceedsSourceLimit,
        ChargeExpiredForCapture,
        ChargeInvalidParameter,
        ClearingCodeUnsupported,
        CountryCodeInvalid,
        CountryUnsupported,
        CouponExpired,
        CustomerMaxPaymentMethods,
        CustomerMaxSubscriptions,
        DebitNotAuthorized,
        EmailInvalid,
        ExpiredCard,
        IdempotencyKeyInUse,
        IncorrectAddress,
        IncorrectCvc,
        IncorrectNumber,
        IncorrectZip,
        InstantPayoutsConfigDisabled,
        InstantPayoutsCurrencyDisabled,
        InstantPayoutsLimitExceeded,
        InstantPayoutsUnsupported,
        InsufficientFunds,
        IntentInvalidState,
        IntentVerificationMethodMissing,
        InvalidCardType,
        InvalidCharacters,
        InvalidChargeAmount,
        InvalidCvc,
        InvalidExpiryMonth,
        InvalidExpiryYear,
        InvalidNumber,
        InvalidSourceUsage,
        InvoiceNoCustomerLineItems,
        InvoiceNoPaymentMethodTypes,
        InvoiceNoSubscriptionLineItems,
        InvoiceNotEditable,
        InvoiceOnBehalfOfNotEditable,
        InvoicePaymentIntentRequiresAction,
        InvoiceUpcomingNone,
        LivemodeMismatch,
        LockTimeout,
        Missing,
        NoAccount,
        NotAllowedOnStandardAccount,
        OutOfInventory,
        ParameterInvalidEmpty,
        ParameterInvalidInteger,
        ParameterInvalidStringBlank,
        ParameterInvalidStringEmpty,
        ParametersExclusive,
        PaymentIntentActionRequired,
        PaymentIntentIncompatiblePaymentMethod,
        PaymentIntentInvalidParameter,
        PaymentIntentKonbiniRejectedConfirmationNumber,
        PaymentIntentPaymentAttemptExpired,
        PaymentIntentUnexpectedState,
        PaymentMethodBankAccountAlreadyVerified,
        PaymentMethodBankAccountBlocked,
        PaymentMethodBillingDetailsAddressMissing,
        PaymentMethodCurrencyMismatch,
        PaymentMethodInvalidParameter,
        PaymentMethodInvalidParameterTestmode,
        PaymentMethodMicrodepositFailed,
        PaymentMethodMicrodepositVerificationAmountsInvalid,
        PaymentMethodMicrodepositVerificationAmountsMismatch,
        PaymentMethodMicrodepositVerificationAttemptsExceeded,
        PaymentMethodMicrodepositVerificationDescriptorCodeMismatch,
        PaymentMethodMicrodepositVerificationTimeout,
        PaymentMethodProviderDecline,
        PaymentMethodProviderTimeout,
        PaymentMethodUnexpectedState,
        PaymentMethodUnsupportedType,
        PayoutsNotAllowed,
        PlatformAccountRequired,
        PlatformApiKeyExpired,
        PostalCodeInvalid,
        ProcessingError,
        ProductInactive,
        RateLimit,
        ReferToCustomer,
        RefundDisputedPayment,
        ResourceAlreadyExists,
        ResourceMissing,
        ReturnIntentAlreadyProcessed,
        RoutingNumberInvalid,
        SecretKeyRequired,
        SepaUnsupportedAccount,
        SetupAttemptFailed,
        SetupIntentAuthenticationFailure,
        SetupIntentInvalidParameter,
        SetupIntentSetupAttemptExpired,
        SetupIntentUnexpectedState,
        ShippingCalculationFailed,
        SkuInactive,
        StateUnsupported,
        StatusTransitionInvalid,
        TaxIdInvalid,
        TaxesCalculationFailed,
        TerminalLocationCountryUnsupported,
        TestmodeChargesOnly,
        TlsVersionUnsupported,
        TokenInUse,
        TransferSourceBalanceParametersMismatch,
        TransfersNotAllowed,
    */
}

impl ::core::fmt::Display for StripeErrorCode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{{\"error\": {}}}",
            serde_json::to_string(self).unwrap_or_else(|_| "API error response".to_string())
        )
    }
}

#[derive(Clone, Debug, serde::Serialize)]
#[serde(rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
pub enum StripeErrorType {
    ApiError,
    CardError,
    InvalidRequestError,
    ConnectorError,
    HyperswitchError,
}

impl From<errors::ApiErrorResponse> for StripeErrorCode {
    fn from(value: errors::ApiErrorResponse) -> Self {
        match value {
            errors::ApiErrorResponse::Unauthorized
            | errors::ApiErrorResponse::InvalidJwtToken
            | errors::ApiErrorResponse::GenericUnauthorized { .. }
            | errors::ApiErrorResponse::AccessForbidden { .. }
            | errors::ApiErrorResponse::InvalidCookie
            | errors::ApiErrorResponse::InvalidEphemeralKey
            | errors::ApiErrorResponse::InvalidPaymentIdProvided { .. }
            | errors::ApiErrorResponse::CookieNotFound => Self::Unauthorized,
            errors::ApiErrorResponse::InvalidRequestUrl
            | errors::ApiErrorResponse::InvalidHttpMethod
            | errors::ApiErrorResponse::InvalidCardIin
            | errors::ApiErrorResponse::InvalidCardIinLength => Self::InvalidRequestUrl,
            errors::ApiErrorResponse::MissingRequiredField { field_name } => {
                Self::ParameterMissing {
                    field_name: field_name.to_string(),
                    param: field_name.to_string(),
                }
            }
            errors::ApiErrorResponse::UnprocessableEntity { message } => {
                Self::HyperswitchUnprocessableEntity { message }
            }
            errors::ApiErrorResponse::MissingRequiredFields { field_names } => {
                // Instead of creating a new error variant in StripeErrorCode for MissingRequiredFields, converted vec<&str> to String
                Self::ParameterMissing {
                    field_name: field_names.clone().join(", "),
                    param: field_names.clone().join(", "),
                }
            }
            errors::ApiErrorResponse::GenericNotFoundError { message } => {
                Self::GenericNotFoundError { message }
            }
            errors::ApiErrorResponse::GenericDuplicateError { message } => {
                Self::GenericDuplicateError { message }
            }
            // parameter unknown, invalid request error // actually if we type wrong values in address we get this error. Stripe throws parameter unknown. I don't know if stripe is validating email and stuff
            errors::ApiErrorResponse::InvalidDataFormat {
                field_name,
                expected_format,
            } => Self::ParameterUnknown {
                field_name,
                expected_format,
            },
            errors::ApiErrorResponse::RefundAmountExceedsPaymentAmount => {
                Self::RefundAmountExceedsPaymentAmount {
                    param: "amount".to_owned(),
                }
            }
            errors::ApiErrorResponse::PaymentAuthorizationFailed { data }
            | errors::ApiErrorResponse::PaymentAuthenticationFailed { data } => {
                Self::PaymentIntentAuthenticationFailure { data }
            }
            errors::ApiErrorResponse::VerificationFailed { data } => {
                Self::VerificationFailed { data }
            }
            errors::ApiErrorResponse::PaymentCaptureFailed { data } => {
                Self::PaymentIntentPaymentAttemptFailed { data }
            }
            errors::ApiErrorResponse::DisputeFailed { data } => Self::DisputeFailed { data },
            errors::ApiErrorResponse::InvalidCardData { data: _ } => Self::InvalidCardType, // Maybe it is better to de generalize this router error
            errors::ApiErrorResponse::CardExpired { data: _ } => Self::ExpiredCard,
            errors::ApiErrorResponse::RefundNotPossible { connector: _ } => Self::RefundFailed,
            errors::ApiErrorResponse::RefundFailed { data: _ } => Self::RefundFailed, // Nothing at stripe to map
            errors::ApiErrorResponse::PayoutFailed { data: _ } => Self::PayoutFailed,
            errors::ApiErrorResponse::ExternalVaultFailed => Self::ExternalVaultFailed,

            errors::ApiErrorResponse::MandateUpdateFailed
            | errors::ApiErrorResponse::MandateSerializationFailed
            | errors::ApiErrorResponse::MandateDeserializationFailed
            | errors::ApiErrorResponse::InternalServerError
            | errors::ApiErrorResponse::HealthCheckError { .. } => Self::InternalServerError, // not a stripe code
            errors::ApiErrorResponse::ExternalConnectorError {
                code,
                message,
                connector,
                status_code,
                ..
            } => Self::ExternalConnectorError {
                code,
                message,
                connector,
                status_code,
            },
            errors::ApiErrorResponse::IncorrectConnectorNameGiven => {
                Self::IncorrectConnectorNameGiven
            }
            errors::ApiErrorResponse::MandateActive => Self::MandateActive, //not a stripe code
            errors::ApiErrorResponse::CustomerRedacted => Self::CustomerRedacted, //not a stripe code
            errors::ApiErrorResponse::ConfigNotFound => Self::ConfigNotFound, // not a stripe code
            errors::ApiErrorResponse::DuplicateConfig => Self::DuplicateConfig, // not a stripe code
            errors::ApiErrorResponse::DuplicateRefundRequest => Self::DuplicateRefundRequest,
            errors::ApiErrorResponse::DuplicatePayout { payout_id } => {
                Self::DuplicatePayout { payout_id }
            }
            errors::ApiErrorResponse::RefundNotFound => Self::RefundNotFound,
            errors::ApiErrorResponse::CustomerNotFound => Self::CustomerNotFound,
            errors::ApiErrorResponse::PaymentNotFound => Self::PaymentNotFound,
            errors::ApiErrorResponse::PaymentMethodNotFound => Self::PaymentMethodNotFound,
            errors::ApiErrorResponse::ClientSecretNotGiven
            | errors::ApiErrorResponse::ClientSecretExpired => Self::ClientSecretNotFound,
            errors::ApiErrorResponse::MerchantAccountNotFound => Self::MerchantAccountNotFound,
            errors::ApiErrorResponse::PaymentLinkNotFound => Self::PaymentLinkNotFound,
            errors::ApiErrorResponse::ResourceIdNotFound => Self::ResourceIdNotFound,
            errors::ApiErrorResponse::MerchantConnectorAccountNotFound { id } => {
                Self::MerchantConnectorAccountNotFound { id }
            }
            errors::ApiErrorResponse::MandateNotFound => Self::MandateNotFound,
            errors::ApiErrorResponse::ApiKeyNotFound => Self::ApiKeyNotFound,
            errors::ApiErrorResponse::PayoutNotFound => Self::PayoutNotFound,
            errors::ApiErrorResponse::EventNotFound => Self::EventNotFound,
            errors::ApiErrorResponse::MandateValidationFailed { reason } => {
                Self::PaymentIntentMandateInvalid { message: reason }
            }
            errors::ApiErrorResponse::ReturnUrlUnavailable => Self::ReturnUrlUnavailable,
            errors::ApiErrorResponse::DuplicateMerchantAccount => Self::DuplicateMerchantAccount,
            errors::ApiErrorResponse::DuplicateMerchantConnectorAccount {
                profile_id,
                connector_label,
            } => Self::DuplicateMerchantConnectorAccount {
                profile_id,
                connector_label,
            },
            errors::ApiErrorResponse::DuplicatePaymentMethod => Self::DuplicatePaymentMethod,
            errors::ApiErrorResponse::PaymentBlockedError {
                code,
                message,
                status,
                reason,
            } => Self::PaymentBlockedError {
                code,
                message,
                status,
                reason,
            },
            errors::ApiErrorResponse::ClientSecretInvalid => Self::PaymentIntentInvalidParameter {
                param: "client_secret".to_owned(),
            },
            errors::ApiErrorResponse::InvalidRequestData { message } => {
                Self::InvalidRequestData { message }
            }
            errors::ApiErrorResponse::PreconditionFailed { message } => {
                Self::PreconditionFailed { message }
            }
            errors::ApiErrorResponse::InvalidDataValue { field_name } => Self::ParameterMissing {
                field_name: field_name.to_string(),
                param: field_name.to_string(),
            },
            errors::ApiErrorResponse::MaximumRefundCount => Self::MaximumRefundCount,
            errors::ApiErrorResponse::PaymentNotSucceeded => Self::PaymentFailed,
            errors::ApiErrorResponse::DuplicateMandate => Self::DuplicateMandate,
            errors::ApiErrorResponse::SuccessfulPaymentNotFound => Self::SuccessfulPaymentNotFound,
            errors::ApiErrorResponse::AddressNotFound => Self::AddressNotFound,
            errors::ApiErrorResponse::NotImplemented { .. } => Self::Unauthorized,
            errors::ApiErrorResponse::FlowNotSupported { .. } => Self::InternalServerError,
            errors::ApiErrorResponse::MandatePaymentDataMismatch { .. } => Self::PlatformBadRequest,
            errors::ApiErrorResponse::MaxFieldLengthViolated { .. } => Self::PlatformBadRequest,
            errors::ApiErrorResponse::PaymentUnexpectedState {
                current_flow,
                field_name,
                current_value,
                states,
            } => Self::PaymentIntentUnexpectedState {
                current_flow,
                field_name,
                current_value,
                states,
            },
            errors::ApiErrorResponse::DuplicatePayment { payment_id } => {
                Self::DuplicatePayment { payment_id }
            }
            errors::ApiErrorResponse::DisputeNotFound { dispute_id } => Self::ResourceMissing {
                object: "dispute".to_owned(),
                id: dispute_id,
            },
            errors::ApiErrorResponse::AuthenticationNotFound { id } => Self::ResourceMissing {
                object: "authentication".to_owned(),
                id,
            },
            errors::ApiErrorResponse::ProfileNotFound { id } => Self::ResourceMissing {
                object: "business_profile".to_owned(),
                id,
            },
            errors::ApiErrorResponse::PollNotFound { id } => Self::ResourceMissing {
                object: "poll".to_owned(),
                id,
            },
            errors::ApiErrorResponse::DisputeStatusValidationFailed { reason: _ } => {
                Self::InternalServerError
            }
            errors::ApiErrorResponse::FileValidationFailed { .. } => Self::FileValidationFailed,
            errors::ApiErrorResponse::MissingFile => Self::MissingFile,
            errors::ApiErrorResponse::MissingFilePurpose => Self::MissingFilePurpose,
            errors::ApiErrorResponse::MissingFileContentType => Self::MissingFileContentType,
            errors::ApiErrorResponse::MissingDisputeId => Self::MissingDisputeId,
            errors::ApiErrorResponse::FileNotFound => Self::FileNotFound,
            errors::ApiErrorResponse::FileNotAvailable => Self::FileNotAvailable,
            errors::ApiErrorResponse::MerchantConnectorAccountDisabled => {
                Self::MerchantConnectorAccountDisabled
            }
            errors::ApiErrorResponse::NotSupported { .. } => Self::InternalServerError,
            errors::ApiErrorResponse::CurrencyNotSupported { message } => {
                Self::CurrencyNotSupported { message }
            }
            errors::ApiErrorResponse::FileProviderNotSupported { .. } => {
                Self::FileProviderNotSupported
            }
            errors::ApiErrorResponse::WebhookBadRequest
            | errors::ApiErrorResponse::WebhookResourceNotFound
            | errors::ApiErrorResponse::WebhookProcessingFailure
            | errors::ApiErrorResponse::WebhookAuthenticationFailed
            | errors::ApiErrorResponse::WebhookUnprocessableEntity
            | errors::ApiErrorResponse::WebhookInvalidMerchantSecret => {
                Self::WebhookProcessingError
            }
            errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration => {
                Self::PaymentMethodUnactivated
            }
            errors::ApiErrorResponse::ResourceBusy => Self::PaymentMethodUnactivated,
            errors::ApiErrorResponse::InvalidConnectorConfiguration { config } => {
                Self::InvalidConnectorConfiguration { config }
            }
            errors::ApiErrorResponse::CurrencyConversionFailed => Self::CurrencyConversionFailed,
            errors::ApiErrorResponse::PaymentMethodDeleteFailed => Self::PaymentMethodDeleteFailed,
            errors::ApiErrorResponse::InvalidWalletToken { wallet_name } => {
                Self::InvalidWalletToken { wallet_name }
            }
            errors::ApiErrorResponse::ExtendedCardInfoNotFound => Self::ExtendedCardInfoNotFound,
            errors::ApiErrorResponse::LinkConfigurationError { message } => {
                Self::LinkConfigurationError { message }
            }
            errors::ApiErrorResponse::IntegrityCheckFailed {
                reason,
                field_names,
                connector_transaction_id,
            } => Self::IntegrityCheckFailed {
                reason,
                field_names,
                connector_transaction_id,
            },
            errors::ApiErrorResponse::InvalidTenant { tenant_id: _ }
            | errors::ApiErrorResponse::MissingTenantId => Self::InvalidTenant,
            errors::ApiErrorResponse::AmountConversionFailed { amount_type } => {
                Self::AmountConversionFailed { amount_type }
            }
            errors::ApiErrorResponse::PlatformAccountAuthNotSupported => Self::PlatformBadRequest,
            errors::ApiErrorResponse::InvalidPlatformOperation => Self::PlatformUnauthorizedRequest,
            errors::ApiErrorResponse::ProfileAcquirerNotFound { .. } => {
                Self::ProfileAcquirerNotFound
            }
            errors::ApiErrorResponse::TokenizationRecordNotFound { id } => Self::ResourceMissing {
                object: "tokenization record".to_owned(),
                id,
            },
            errors::ApiErrorResponse::SubscriptionError { operation } => {
                Self::SubscriptionError { operation }
            }
        }
    }
}

impl actix_web::ResponseError for StripeErrorCode {
    fn status_code(&self) -> reqwest::StatusCode {
        use reqwest::StatusCode;

        match self {
            Self::Unauthorized | Self::PlatformUnauthorizedRequest => StatusCode::UNAUTHORIZED,
            Self::InvalidRequestUrl | Self::GenericNotFoundError { .. } => StatusCode::NOT_FOUND,
            Self::ParameterUnknown { .. } | Self::HyperswitchUnprocessableEntity { .. } => {
                StatusCode::UNPROCESSABLE_ENTITY
            }
            Self::ParameterMissing { .. }
            | Self::RefundAmountExceedsPaymentAmount { .. }
            | Self::PaymentIntentAuthenticationFailure { .. }
            | Self::PaymentIntentPaymentAttemptFailed { .. }
            | Self::ExpiredCard
            | Self::InvalidCardType
            | Self::DuplicateRefundRequest
            | Self::DuplicatePayout { .. }
            | Self::RefundNotFound
            | Self::CustomerNotFound
            | Self::ConfigNotFound
            | Self::DuplicateConfig
            | Self::ClientSecretNotFound
            | Self::PaymentNotFound
            | Self::PaymentMethodNotFound
            | Self::MerchantAccountNotFound
            | Self::MerchantConnectorAccountNotFound { .. }
            | Self::MerchantConnectorAccountDisabled
            | Self::MandateNotFound
            | Self::ApiKeyNotFound
            | Self::PayoutNotFound
            | Self::EventNotFound
            | Self::DuplicateMerchantAccount
            | Self::DuplicateMerchantConnectorAccount { .. }
            | Self::DuplicatePaymentMethod
            | Self::PaymentFailed
            | Self::VerificationFailed { .. }
            | Self::DisputeFailed { .. }
            | Self::MaximumRefundCount
            | Self::PaymentIntentInvalidParameter { .. }
            | Self::SerdeQsError { .. }
            | Self::InvalidRequestData { .. }
            | Self::InvalidWalletToken { .. }
            | Self::PreconditionFailed { .. }
            | Self::DuplicateMandate
            | Self::SuccessfulPaymentNotFound
            | Self::AddressNotFound
            | Self::ResourceIdNotFound
            | Self::PaymentIntentMandateInvalid { .. }
            | Self::PaymentIntentUnexpectedState { .. }
            | Self::DuplicatePayment { .. }
            | Self::GenericDuplicateError { .. }
            | Self::IncorrectConnectorNameGiven
            | Self::ResourceMissing { .. }
            | Self::FileValidationFailed
            | Self::MissingFile
            | Self::MissingFileContentType
            | Self::MissingFilePurpose
            | Self::MissingDisputeId
            | Self::FileNotFound
            | Self::FileNotAvailable
            | Self::FileProviderNotSupported
            | Self::CurrencyNotSupported { .. }
            | Self::DuplicateCustomer
            | Self::PaymentMethodUnactivated
            | Self::InvalidConnectorConfiguration { .. }
            | Self::CurrencyConversionFailed
            | Self::PaymentMethodDeleteFailed
            | Self::ExtendedCardInfoNotFound
            | Self::PlatformBadRequest
            | Self::LinkConfigurationError { .. } => StatusCode::BAD_REQUEST,
            Self::RefundFailed
            | Self::PayoutFailed
            | Self::PaymentLinkNotFound
            | Self::InternalServerError
            | Self::MandateActive
            | Self::CustomerRedacted
            | Self::WebhookProcessingError
            | Self::InvalidTenant
            | Self::ExternalVaultFailed
            | Self::AmountConversionFailed { .. }
            | Self::SubscriptionError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            Self::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE,
            Self::ExternalConnectorError { status_code, .. } => {
                StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
            }
            Self::IntegrityCheckFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            Self::PaymentBlockedError { code, .. } => {
                StatusCode::from_u16(*code).unwrap_or(StatusCode::OK)
            }
            Self::LockTimeout => StatusCode::LOCKED,
            Self::ProfileAcquirerNotFound => StatusCode::NOT_FOUND,
        }
    }

    fn error_response(&self) -> actix_web::HttpResponse {
        use actix_web::http::header;

        actix_web::HttpResponseBuilder::new(self.status_code())
            .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
            .body(self.to_string())
    }
}

impl From<serde_qs::Error> for StripeErrorCode {
    fn from(item: serde_qs::Error) -> Self {
        match item {
            serde_qs::Error::Custom(s) => Self::SerdeQsError {
                error_message: s,
                param: None,
            },
            serde_qs::Error::Parse(param, position) => Self::SerdeQsError {
                error_message: format!(
                    "parsing failed with error: '{param}' at position: {position}"
                ),
                param: Some(param),
            },
            serde_qs::Error::Unsupported => Self::SerdeQsError {
                error_message: "Given request format is not supported".to_owned(),
                param: None,
            },
            serde_qs::Error::FromUtf8(_) => Self::SerdeQsError {
                error_message: "Failed to parse request to from utf-8".to_owned(),
                param: None,
            },
            serde_qs::Error::Io(_) => Self::SerdeQsError {
                error_message: "Failed to parse request".to_owned(),
                param: None,
            },
            serde_qs::Error::ParseInt(_) => Self::SerdeQsError {
                error_message: "Failed to parse integer in request".to_owned(),
                param: None,
            },
            serde_qs::Error::Utf8(_) => Self::SerdeQsError {
                error_message: "Failed to convert utf8 to string".to_owned(),
                param: None,
            },
        }
    }
}

impl ErrorSwitch<StripeErrorCode> for errors::ApiErrorResponse {
    fn switch(&self) -> StripeErrorCode {
        self.clone().into()
    }
}

impl crate::services::EmbedError for error_stack::Report<StripeErrorCode> {}

impl ErrorSwitch<StripeErrorCode> for CustomersErrorResponse {
    fn switch(&self) -> StripeErrorCode {
        use StripeErrorCode as SC;
        match self {
            Self::CustomerRedacted => SC::CustomerRedacted,
            Self::InternalServerError => SC::InternalServerError,
            Self::InvalidRequestData { message } => SC::InvalidRequestData {
                message: message.clone(),
            },
            Self::MandateActive => SC::MandateActive,
            Self::CustomerNotFound => SC::CustomerNotFound,
            Self::CustomerAlreadyExists => SC::DuplicateCustomer,
        }
    }
}


// File: crates/router/src/compatibility/stripe/customers.rs

pub mod types;
#[cfg(feature = "v1")]
use actix_web::{web, HttpRequest, HttpResponse};
#[cfg(feature = "v1")]
use common_utils::id_type;
#[cfg(feature = "v1")]
use error_stack::report;
#[cfg(feature = "v1")]
use router_env::{instrument, tracing, Flow};

#[cfg(feature = "v1")]
use crate::{
    compatibility::{stripe::errors, wrap},
    core::{api_locking, customers, payment_methods::cards},
    routes,
    services::{api, authentication as auth},
    types::api::{customers as customer_types, payment_methods},
};

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::CustomersCreate))]
pub async fn customer_create(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
) -> HttpResponse {
    let payload: types::CreateCustomerRequest = match qs_config.deserialize_bytes(&form_payload) {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

    let create_cust_req: customer_types::CustomerRequest = payload.into();

    let flow = Flow::CustomersCreate;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::CreateCustomerResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        create_cust_req,
        |state, auth: auth::AuthenticationData, req, _| {
            let platform = auth.into();
            customers::create_customer(state, platform, req, None)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::CustomersRetrieve))]
pub async fn customer_retrieve(
    state: web::Data<routes::AppState>,
    req: HttpRequest,
    path: web::Path<id_type::CustomerId>,
) -> HttpResponse {
    let customer_id = path.into_inner();

    let flow = Flow::CustomersRetrieve;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::CustomerRetrieveResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        customer_id,
        |state, auth: auth::AuthenticationData, customer_id, _| {
            let platform = auth.into();
            customers::retrieve_customer(state, platform, None, customer_id)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::CustomersUpdate))]
pub async fn customer_update(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    path: web::Path<id_type::CustomerId>,
    form_payload: web::Bytes,
) -> HttpResponse {
    let payload: types::CustomerUpdateRequest = match qs_config.deserialize_bytes(&form_payload) {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

    let customer_id = path.into_inner().clone();
    let request = customer_types::CustomerUpdateRequest::from(payload);
    let request_internal = customer_types::CustomerUpdateRequestInternal {
        customer_id,
        request,
    };

    let flow = Flow::CustomersUpdate;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::CustomerUpdateResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        request_internal,
        |state, auth: auth::AuthenticationData, request_internal, _| {
            let platform = auth.into();
            customers::update_customer(state, platform, request_internal)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::CustomersDelete))]
pub async fn customer_delete(
    state: web::Data<routes::AppState>,
    req: HttpRequest,
    path: web::Path<id_type::CustomerId>,
) -> HttpResponse {
    let customer_id = path.into_inner();

    let flow = Flow::CustomersDelete;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::CustomerDeleteResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        customer_id,
        |state, auth: auth::AuthenticationData, customer_id, _| {
            let platform = auth.into();
            customers::delete_customer(state, platform, customer_id)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))]
pub async fn list_customer_payment_method_api(
    state: web::Data<routes::AppState>,
    req: HttpRequest,
    path: web::Path<id_type::CustomerId>,
    json_payload: web::Query<payment_methods::PaymentMethodListRequest>,
) -> HttpResponse {
    let payload = json_payload.into_inner();
    let customer_id = path.into_inner();
    let flow = Flow::CustomerPaymentMethodsList;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::CustomerPaymentMethodListResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let platform = auth.into();
            cards::do_list_customer_pm_fetch_customer_if_not_passed(
                state,
                platform,
                Some(req),
                Some(&customer_id),
                None,
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


// File: crates/router/src/compatibility/stripe/setup_intents.rs

pub mod types;
#[cfg(feature = "v1")]
use actix_web::{web, HttpRequest, HttpResponse};
#[cfg(feature = "v1")]
use api_models::payments as payment_types;
#[cfg(feature = "v1")]
use error_stack::report;
#[cfg(feature = "v1")]
use router_env::{instrument, tracing, Flow};

#[cfg(feature = "v1")]
use crate::{
    compatibility::{
        stripe::{errors, payment_intents::types as stripe_payment_types},
        wrap,
    },
    core::{api_locking, payments},
    routes,
    services::{api, authentication as auth},
    types::api as api_types,
};

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate))]
pub async fn setup_intents_create(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
) -> HttpResponse {
    let payload: types::StripeSetupIntentRequest = match qs_config.deserialize_bytes(&form_payload)
    {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

    let create_payment_req: payment_types::PaymentsRequest =
        match payment_types::PaymentsRequest::try_from(payload) {
            Ok(req) => req,
            Err(err) => return api::log_and_return_error_response(err),
        };

    let flow = Flow::PaymentsCreate;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripeSetupIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        create_payment_req,
        |state, auth: auth::AuthenticationData, req, req_state| {
            let platform = auth.into();
            payments::payments_core::<
                api_types::SetupMandate,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::SetupMandate>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentCreate,
                req,
                api::AuthFlow::Merchant,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsRetrieveForceSync))]
pub async fn setup_intents_retrieve(
    state: web::Data<routes::AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::PaymentId>,
    query_payload: web::Query<stripe_payment_types::StripePaymentRetrieveBody>,
) -> HttpResponse {
    let payload = payment_types::PaymentsRetrieveRequest {
        resource_id: api_types::PaymentIdType::PaymentIntentId(path.into_inner()),
        merchant_id: None,
        force_sync: true,
        connector: None,
        param: None,
        merchant_connector_details: None,
        client_secret: query_payload.client_secret.clone(),
        expand_attempts: None,
        expand_captures: None,
        all_keys_required: None,
    };

    let api_auth = auth::ApiKeyAuth {
        is_connected_allowed: false,
        is_platform_allowed: false,
    };

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

    let flow = Flow::PaymentsRetrieveForceSync;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripeSetupIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth, payload, req_state| {
            let platform = auth.into();
            payments::payments_core::<
                api_types::PSync,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::PSync>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentStatus,
                payload,
                auth_flow,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &*auth_type,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsUpdate))]
pub async fn setup_intents_update(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
    path: web::Path<common_utils::id_type::PaymentId>,
) -> HttpResponse {
    let setup_id = path.into_inner();
    let stripe_payload: types::StripeSetupIntentRequest = match qs_config
        .deserialize_bytes(&form_payload)
    {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

    let mut payload: payment_types::PaymentsRequest =
        match payment_types::PaymentsRequest::try_from(stripe_payload) {
            Ok(req) => req,
            Err(err) => return api::log_and_return_error_response(err),
        };
    payload.payment_id = Some(api_types::PaymentIdType::PaymentIntentId(setup_id));

    let api_auth = auth::ApiKeyAuth {
        is_connected_allowed: false,
        is_platform_allowed: false,
    };

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

    let flow = Flow::PaymentsUpdate;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripeSetupIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth, req, req_state| {
            let platform = auth.into();
            payments::payments_core::<
                api_types::SetupMandate,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::SetupMandate>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentUpdate,
                req,
                auth_flow,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &*auth_type,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsConfirm))]
pub async fn setup_intents_confirm(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
    path: web::Path<common_utils::id_type::PaymentId>,
) -> HttpResponse {
    let setup_id = path.into_inner();
    let stripe_payload: types::StripeSetupIntentRequest = match qs_config
        .deserialize_bytes(&form_payload)
    {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

    let mut payload: payment_types::PaymentsRequest =
        match payment_types::PaymentsRequest::try_from(stripe_payload) {
            Ok(req) => req,
            Err(err) => return api::log_and_return_error_response(err),
        };
    payload.payment_id = Some(api_types::PaymentIdType::PaymentIntentId(setup_id));
    payload.confirm = Some(true);

    let api_auth = auth::ApiKeyAuth {
        is_connected_allowed: false,
        is_platform_allowed: false,
    };

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

    let flow = Flow::PaymentsConfirm;

    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripeSetupIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth, req, req_state| {
            let platform = auth.into();
            payments::payments_core::<
                api_types::SetupMandate,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::SetupMandate>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentConfirm,
                req,
                auth_flow,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &*auth_type,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


// File: crates/router/src/compatibility/stripe/app.rs

use actix_web::{web, Scope};

#[cfg(feature = "v1")]
use super::{customers::*, payment_intents::*, setup_intents::*};
use super::{refunds::*, webhooks::*};
use crate::routes::{self, mandates, webhooks};

pub struct PaymentIntents;

#[cfg(feature = "v1")]
impl PaymentIntents {
    pub fn server(state: routes::AppState) -> Scope {
        let mut route = web::scope("/payment_intents").app_data(web::Data::new(state));
        #[cfg(feature = "olap")]
        {
            route = route.service(web::resource("/list").route(web::get().to(payment_intent_list)))
        }
        route = route
            .service(web::resource("").route(web::post().to(payment_intents_create)))
            .service(
                web::resource("/sync")
                    .route(web::post().to(payment_intents_retrieve_with_gateway_creds)),
            )
            .service(
                web::resource("/{payment_id}")
                    .route(web::get().to(payment_intents_retrieve))
                    .route(web::post().to(payment_intents_update)),
            )
            .service(
                web::resource("/{payment_id}/confirm")
                    .route(web::post().to(payment_intents_confirm)),
            )
            .service(
                web::resource("/{payment_id}/capture")
                    .route(web::post().to(payment_intents_capture)),
            )
            .service(
                web::resource("/{payment_id}/cancel").route(web::post().to(payment_intents_cancel)),
            );
        route
    }
}

pub struct SetupIntents;

#[cfg(feature = "v1")]
impl SetupIntents {
    pub fn server(state: routes::AppState) -> Scope {
        web::scope("/setup_intents")
            .app_data(web::Data::new(state))
            .service(web::resource("").route(web::post().to(setup_intents_create)))
            .service(
                web::resource("/{setup_id}")
                    .route(web::get().to(setup_intents_retrieve))
                    .route(web::post().to(setup_intents_update)),
            )
            .service(
                web::resource("/{setup_id}/confirm").route(web::post().to(setup_intents_confirm)),
            )
    }
}

pub struct Refunds;

impl Refunds {
    pub fn server(config: routes::AppState) -> Scope {
        web::scope("/refunds")
            .app_data(web::Data::new(config))
            .service(web::resource("").route(web::post().to(refund_create)))
            .service(
                web::resource("/sync").route(web::post().to(refund_retrieve_with_gateway_creds)),
            )
            .service(
                web::resource("/{refund_id}")
                    .route(web::get().to(refund_retrieve))
                    .route(web::post().to(refund_update)),
            )
    }
}

pub struct Customers;

#[cfg(feature = "v1")]
impl Customers {
    pub fn server(config: routes::AppState) -> Scope {
        web::scope("/customers")
            .app_data(web::Data::new(config))
            .service(web::resource("").route(web::post().to(customer_create)))
            .service(
                web::resource("/{customer_id}")
                    .route(web::get().to(customer_retrieve))
                    .route(web::post().to(customer_update))
                    .route(web::delete().to(customer_delete)),
            )
            .service(
                web::resource("/{customer_id}/payment_methods")
                    .route(web::get().to(list_customer_payment_method_api)),
            )
    }
}

pub struct Webhooks;

impl Webhooks {
    pub fn server(config: routes::AppState) -> Scope {
        web::scope("/webhooks")
            .app_data(web::Data::new(config))
            .service(
                web::resource("/{merchant_id}/{connector_name}")
                    .route(
                        web::post().to(webhooks::receive_incoming_webhook::<StripeOutgoingWebhook>),
                    )
                    .route(
                        web::get().to(webhooks::receive_incoming_webhook::<StripeOutgoingWebhook>),
                    ),
            )
    }
}

pub struct Mandates;

impl Mandates {
    pub fn server(config: routes::AppState) -> Scope {
        web::scope("/payment_methods")
            .app_data(web::Data::new(config))
            .service(web::resource("/{id}/detach").route(web::post().to(mandates::revoke_mandate)))
    }
}


// File: crates/router/src/compatibility/stripe/customers/types.rs

use std::{convert::From, default::Default};

#[cfg(feature = "v1")]
use api_models::payment_methods as api_types;
use api_models::payments;
#[cfg(feature = "v1")]
use common_utils::{crypto::Encryptable, date_time};
use common_utils::{
    id_type,
    pii::{self, Email},
    types::Description,
};
use serde::{Deserialize, Serialize};

#[cfg(feature = "v1")]
use crate::logger;
use crate::types::{api, api::enums as api_enums};

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
pub struct Shipping {
    pub address: StripeAddressDetails,
    pub name: Option<masking::Secret<String>>,
    pub carrier: Option<String>,
    pub phone: Option<masking::Secret<String>>,
    pub tracking_number: Option<masking::Secret<String>>,
}

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
pub struct StripeAddressDetails {
    pub city: Option<String>,
    pub country: Option<api_enums::CountryAlpha2>,
    pub line1: Option<masking::Secret<String>>,
    pub line2: Option<masking::Secret<String>>,
    pub postal_code: Option<masking::Secret<String>>,
    pub state: Option<masking::Secret<String>>,
}

#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateCustomerRequest {
    pub email: Option<Email>,
    pub invoice_prefix: Option<String>,
    pub name: Option<masking::Secret<String>>,
    pub phone: Option<masking::Secret<String>>,
    pub address: Option<StripeAddressDetails>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub description: Option<Description>,
    pub shipping: Option<Shipping>,
    pub payment_method: Option<String>,              // not used
    pub balance: Option<i64>,                        // not used
    pub cash_balance: Option<pii::SecretSerdeValue>, // not used
    pub coupon: Option<String>,                      // not used
    pub invoice_settings: Option<pii::SecretSerdeValue>, // not used
    pub next_invoice_sequence: Option<String>,       // not used
    pub preferred_locales: Option<String>,           // not used
    pub promotion_code: Option<String>,              // not used
    pub source: Option<String>,                      // not used
    pub tax: Option<pii::SecretSerdeValue>,          // not used
    pub tax_exempt: Option<String>,                  // not used
    pub tax_id_data: Option<String>,                 // not used
    pub test_clock: Option<String>,                  // not used
}

#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct CustomerUpdateRequest {
    pub description: Option<Description>,
    pub email: Option<Email>,
    pub phone: Option<masking::Secret<String, masking::WithType>>,
    pub name: Option<masking::Secret<String>>,
    pub address: Option<StripeAddressDetails>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub shipping: Option<Shipping>,
    pub payment_method: Option<String>,              // not used
    pub balance: Option<i64>,                        // not used
    pub cash_balance: Option<pii::SecretSerdeValue>, // not used
    pub coupon: Option<String>,                      // not used
    pub default_source: Option<String>,              // not used
    pub invoice_settings: Option<pii::SecretSerdeValue>, // not used
    pub next_invoice_sequence: Option<String>,       // not used
    pub preferred_locales: Option<String>,           // not used
    pub promotion_code: Option<String>,              // not used
    pub source: Option<String>,                      // not used
    pub tax: Option<pii::SecretSerdeValue>,          // not used
    pub tax_exempt: Option<String>,                  // not used
}

#[derive(Serialize, PartialEq, Eq)]
pub struct CreateCustomerResponse {
    pub id: id_type::CustomerId,
    pub object: String,
    pub created: u64,
    pub description: Option<Description>,
    pub email: Option<Email>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub name: Option<masking::Secret<String>>,
    pub phone: Option<masking::Secret<String, masking::WithType>>,
}

pub type CustomerRetrieveResponse = CreateCustomerResponse;
pub type CustomerUpdateResponse = CreateCustomerResponse;

#[derive(Serialize, PartialEq, Eq)]
pub struct CustomerDeleteResponse {
    pub id: id_type::CustomerId,
    pub deleted: bool,
}

impl From<StripeAddressDetails> for payments::AddressDetails {
    fn from(address: StripeAddressDetails) -> Self {
        Self {
            city: address.city,
            country: address.country,
            line1: address.line1,
            line2: address.line2,
            zip: address.postal_code,
            state: address.state,
            first_name: None,
            line3: None,
            last_name: None,
            origin_zip: None,
        }
    }
}

#[cfg(feature = "v1")]
impl From<CreateCustomerRequest> for api::CustomerRequest {
    fn from(req: CreateCustomerRequest) -> Self {
        Self {
            customer_id: Some(common_utils::generate_customer_id_of_default_length()),
            name: req.name,
            phone: req.phone,
            email: req.email,
            description: req.description,
            metadata: req.metadata,
            address: req.address.map(|s| s.into()),
            ..Default::default()
        }
    }
}

#[cfg(feature = "v1")]
impl From<CustomerUpdateRequest> for api::CustomerUpdateRequest {
    fn from(req: CustomerUpdateRequest) -> Self {
        Self {
            name: req.name,
            phone: req.phone,
            email: req.email,
            description: req.description,
            metadata: req.metadata,
            address: req.address.map(|s| s.into()),
            ..Default::default()
        }
    }
}

#[cfg(feature = "v1")]
impl From<api::CustomerResponse> for CreateCustomerResponse {
    fn from(cust: api::CustomerResponse) -> Self {
        let cust = cust.into_inner();
        Self {
            id: cust.customer_id,
            object: "customer".to_owned(),
            created: u64::try_from(cust.created_at.assume_utc().unix_timestamp()).unwrap_or_else(
                |error| {
                    logger::error!(
                        %error,
                        "incorrect value for `customer.created_at` provided {}", cust.created_at
                    );
                    // Current timestamp converted to Unix timestamp should have a positive value
                    // for many years to come
                    u64::try_from(date_time::now().assume_utc().unix_timestamp())
                        .unwrap_or_default()
                },
            ),
            description: cust.description,
            email: cust.email.map(|inner| inner.into()),
            metadata: cust.metadata,
            name: cust.name.map(Encryptable::into_inner),
            phone: cust.phone.map(Encryptable::into_inner),
        }
    }
}

#[cfg(feature = "v1")]
impl From<api::CustomerDeleteResponse> for CustomerDeleteResponse {
    fn from(cust: api::CustomerDeleteResponse) -> Self {
        Self {
            id: cust.customer_id,
            deleted: cust.customer_deleted,
        }
    }
}

#[derive(Default, Serialize, PartialEq, Eq)]
pub struct CustomerPaymentMethodListResponse {
    pub object: &'static str,
    pub data: Vec<PaymentMethodData>,
}

#[derive(Default, Serialize, PartialEq, Eq)]
pub struct PaymentMethodData {
    pub id: Option<String>,
    pub object: &'static str,
    pub card: Option<CardDetails>,
    pub created: Option<time::PrimitiveDateTime>,
}

#[derive(Default, Serialize, PartialEq, Eq)]
pub struct CardDetails {
    pub country: Option<String>,
    pub last4: Option<String>,
    pub exp_month: Option<masking::Secret<String>>,
    pub exp_year: Option<masking::Secret<String>>,
    pub fingerprint: Option<masking::Secret<String>>,
}

#[cfg(feature = "v1")]
impl From<api::CustomerPaymentMethodsListResponse> for CustomerPaymentMethodListResponse {
    fn from(item: api::CustomerPaymentMethodsListResponse) -> Self {
        let customer_payment_methods = item.customer_payment_methods;
        let data = customer_payment_methods
            .into_iter()
            .map(From::from)
            .collect();
        Self {
            object: "list",
            data,
        }
    }
}

#[cfg(feature = "v1")]
impl From<api_types::CustomerPaymentMethod> for PaymentMethodData {
    fn from(item: api_types::CustomerPaymentMethod) -> Self {
        let card = item.card.map(From::from);
        Self {
            id: Some(item.payment_token),
            object: "payment_method",
            card,
            created: item.created,
        }
    }
}

#[cfg(feature = "v1")]
impl From<api_types::CardDetailFromLocker> for CardDetails {
    fn from(item: api_types::CardDetailFromLocker) -> Self {
        Self {
            country: item.issuer_country,
            last4: item.last4_digits,
            exp_month: item.expiry_month,
            exp_year: item.expiry_year,
            fingerprint: item.card_fingerprint,
        }
    }
}