// File: crates/router_derive/src/macros/api_error/helpers.rs

use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
    parse::Parse, spanned::Spanned, DeriveInput, Field, Fields, LitStr, Token, TypePath, Variant,
};

use crate::macros::helpers::{get_metadata_inner, occurrence_error};

mod keyword {
    use syn::custom_keyword;

    // Enum metadata
    custom_keyword!(error_type_enum);

    // Variant metadata
    custom_keyword!(error_type);
    custom_keyword!(code);
    custom_keyword!(message);
    custom_keyword!(ignore);
}

enum EnumMeta {
    ErrorTypeEnum {
        keyword: keyword::error_type_enum,
        value: TypePath,
    },
}

impl Parse for EnumMeta {
    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(keyword::error_type_enum) {
            let keyword = input.parse()?;
            input.parse::<Token![=]>()?;
            let value = input.parse()?;
            Ok(Self::ErrorTypeEnum { keyword, value })
        } else {
            Err(lookahead.error())
        }
    }
}

impl ToTokens for EnumMeta {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::ErrorTypeEnum { keyword, .. } => keyword.to_tokens(tokens),
        }
    }
}

trait DeriveInputExt {
    /// Get all the error metadata associated with an enum.
    fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>>;
}

impl DeriveInputExt for DeriveInput {
    fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>> {
        get_metadata_inner("error", &self.attrs)
    }
}

pub(super) trait HasErrorTypeProperties {
    fn get_type_properties(&self) -> syn::Result<ErrorTypeProperties>;
}

#[derive(Clone, Debug, Default)]
pub(super) struct ErrorTypeProperties {
    pub error_type_enum: Option<TypePath>,
}

impl HasErrorTypeProperties for DeriveInput {
    fn get_type_properties(&self) -> syn::Result<ErrorTypeProperties> {
        let mut output = ErrorTypeProperties::default();

        let mut error_type_enum_keyword = None;
        for meta in self.get_metadata()? {
            match meta {
                EnumMeta::ErrorTypeEnum { keyword, value } => {
                    if let Some(first_keyword) = error_type_enum_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "error_type_enum"));
                    }

                    error_type_enum_keyword = Some(keyword);
                    output.error_type_enum = Some(value);
                }
            }
        }

        if output.error_type_enum.is_none() {
            return Err(syn::Error::new(
                self.span(),
                "error(error_type_enum) attribute not found",
            ));
        }

        Ok(output)
    }
}

enum VariantMeta {
    ErrorType {
        keyword: keyword::error_type,
        value: TypePath,
    },
    Code {
        keyword: keyword::code,
        value: LitStr,
    },
    Message {
        keyword: keyword::message,
        value: LitStr,
    },
    Ignore {
        keyword: keyword::ignore,
        value: LitStr,
    },
}

impl Parse for VariantMeta {
    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(keyword::error_type) {
            let keyword = input.parse()?;
            let _: Token![=] = input.parse()?;
            let value = input.parse()?;
            Ok(Self::ErrorType { keyword, value })
        } else if lookahead.peek(keyword::code) {
            let keyword = input.parse()?;
            let _: Token![=] = input.parse()?;
            let value = input.parse()?;
            Ok(Self::Code { keyword, value })
        } else if lookahead.peek(keyword::message) {
            let keyword = input.parse()?;
            let _: Token![=] = input.parse()?;
            let value = input.parse()?;
            Ok(Self::Message { keyword, value })
        } else if lookahead.peek(keyword::ignore) {
            let keyword = input.parse()?;
            let _: Token![=] = input.parse()?;
            let value = input.parse()?;
            Ok(Self::Ignore { keyword, value })
        } else {
            Err(lookahead.error())
        }
    }
}

impl ToTokens for VariantMeta {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::ErrorType { keyword, .. } => keyword.to_tokens(tokens),
            Self::Code { keyword, .. } => keyword.to_tokens(tokens),
            Self::Message { keyword, .. } => keyword.to_tokens(tokens),
            Self::Ignore { keyword, .. } => keyword.to_tokens(tokens),
        }
    }
}

trait VariantExt {
    /// Get all the error metadata associated with an enum variant.
    fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>>;
}

impl VariantExt for Variant {
    fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>> {
        get_metadata_inner("error", &self.attrs)
    }
}

pub(super) trait HasErrorVariantProperties {
    fn get_variant_properties(&self) -> syn::Result<ErrorVariantProperties>;
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(super) struct ErrorVariantProperties {
    pub error_type: Option<TypePath>,
    pub code: Option<LitStr>,
    pub message: Option<LitStr>,
    pub ignore: std::collections::HashSet<String>,
}

impl HasErrorVariantProperties for Variant {
    fn get_variant_properties(&self) -> syn::Result<ErrorVariantProperties> {
        let mut output = ErrorVariantProperties::default();

        let mut error_type_keyword = None;
        let mut code_keyword = None;
        let mut message_keyword = None;
        let mut ignore_keyword = None;
        for meta in self.get_metadata()? {
            match meta {
                VariantMeta::ErrorType { keyword, value } => {
                    if let Some(first_keyword) = error_type_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "error_type"));
                    }

                    error_type_keyword = Some(keyword);
                    output.error_type = Some(value);
                }
                VariantMeta::Code { keyword, value } => {
                    if let Some(first_keyword) = code_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "code"));
                    }

                    code_keyword = Some(keyword);
                    output.code = Some(value);
                }
                VariantMeta::Message { keyword, value } => {
                    if let Some(first_keyword) = message_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "message"));
                    }

                    message_keyword = Some(keyword);
                    output.message = Some(value);
                }
                VariantMeta::Ignore { keyword, value } => {
                    if let Some(first_keyword) = ignore_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "ignore"));
                    }
                    ignore_keyword = Some(keyword);
                    output.ignore = value
                        .value()
                        .replace(' ', "")
                        .split(',')
                        .map(ToString::to_string)
                        .collect();
                }
            }
        }

        Ok(output)
    }
}

fn missing_attribute_error(variant: &Variant, attr: &str) -> syn::Error {
    syn::Error::new_spanned(variant, format!("{attr} must be specified"))
}

pub(super) fn check_missing_attributes(
    variant: &Variant,
    variant_properties: &ErrorVariantProperties,
) -> syn::Result<()> {
    if variant_properties.error_type.is_none() {
        return Err(missing_attribute_error(variant, "error_type"));
    }
    if variant_properties.code.is_none() {
        return Err(missing_attribute_error(variant, "code"));
    }
    if variant_properties.message.is_none() {
        return Err(missing_attribute_error(variant, "message"));
    }

    Ok(())
}

/// Get all the fields not used in the error message.
pub(super) fn get_unused_fields(
    fields: &Fields,
    message: &str,
    ignore: &std::collections::HashSet<String>,
) -> Vec<Field> {
    let fields = match fields {
        Fields::Unit => Vec::new(),
        Fields::Unnamed(_) => Vec::new(),
        Fields::Named(fields) => fields.named.iter().cloned().collect(),
    };
    fields
        .iter()
        .filter(|&field| {
            // Safety: Named fields are guaranteed to have an identifier.
            #[allow(clippy::unwrap_used)]
            let field_name = format!("{}", field.ident.as_ref().unwrap());
            !message.contains(&field_name) && !ignore.contains(&field_name)
        })
        .cloned()
        .collect()
}


// File: crates/router_derive/src/macros/schema/helpers.rs

use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{parse::Parse, Field, LitInt, LitStr, Token, TypePath};

use crate::macros::helpers::{get_metadata_inner, occurrence_error};

mod keyword {
    use syn::custom_keyword;

    // Schema metadata
    custom_keyword!(value_type);
    custom_keyword!(min_length);
    custom_keyword!(max_length);
    custom_keyword!(example);
}

pub enum SchemaParameterVariant {
    ValueType {
        keyword: keyword::value_type,
        value: TypePath,
    },
    MinLength {
        keyword: keyword::min_length,
        value: LitInt,
    },
    MaxLength {
        keyword: keyword::max_length,
        value: LitInt,
    },
    Example {
        keyword: keyword::example,
        value: LitStr,
    },
}

impl Parse for SchemaParameterVariant {
    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(keyword::value_type) {
            let keyword = input.parse()?;
            input.parse::<Token![=]>()?;
            let value = input.parse()?;
            Ok(Self::ValueType { keyword, value })
        } else if lookahead.peek(keyword::min_length) {
            let keyword = input.parse()?;
            input.parse::<Token![=]>()?;
            let value = input.parse()?;
            Ok(Self::MinLength { keyword, value })
        } else if lookahead.peek(keyword::max_length) {
            let keyword = input.parse()?;
            input.parse::<Token![=]>()?;
            let value = input.parse()?;
            Ok(Self::MaxLength { keyword, value })
        } else if lookahead.peek(keyword::example) {
            let keyword = input.parse()?;
            input.parse::<Token![=]>()?;
            let value = input.parse()?;
            Ok(Self::Example { keyword, value })
        } else {
            Err(lookahead.error())
        }
    }
}

impl ToTokens for SchemaParameterVariant {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::ValueType { keyword, .. } => keyword.to_tokens(tokens),
            Self::MinLength { keyword, .. } => keyword.to_tokens(tokens),
            Self::MaxLength { keyword, .. } => keyword.to_tokens(tokens),
            Self::Example { keyword, .. } => keyword.to_tokens(tokens),
        }
    }
}

pub trait FieldExt {
    /// Get all the schema metadata associated with a field.
    fn get_schema_metadata(&self) -> syn::Result<Vec<SchemaParameterVariant>>;
}

impl FieldExt for Field {
    fn get_schema_metadata(&self) -> syn::Result<Vec<SchemaParameterVariant>> {
        get_metadata_inner("schema", &self.attrs)
    }
}

#[derive(Clone, Debug, Default)]
pub struct SchemaParameters {
    pub value_type: Option<TypePath>,
    pub min_length: Option<usize>,
    pub max_length: Option<usize>,
    pub example: Option<String>,
}

pub trait HasSchemaParameters {
    fn get_schema_parameters(&self) -> syn::Result<SchemaParameters>;
}

impl HasSchemaParameters for Field {
    fn get_schema_parameters(&self) -> syn::Result<SchemaParameters> {
        let mut output = SchemaParameters::default();

        let mut value_type_keyword = None;
        let mut min_length_keyword = None;
        let mut max_length_keyword = None;
        let mut example_keyword = None;

        for meta in self.get_schema_metadata()? {
            match meta {
                SchemaParameterVariant::ValueType { keyword, value } => {
                    if let Some(first_keyword) = value_type_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "value_type"));
                    }

                    value_type_keyword = Some(keyword);
                    output.value_type = Some(value);
                }
                SchemaParameterVariant::MinLength { keyword, value } => {
                    if let Some(first_keyword) = min_length_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "min_length"));
                    }

                    min_length_keyword = Some(keyword);
                    let min_length = value.base10_parse::<usize>()?;
                    output.min_length = Some(min_length);
                }
                SchemaParameterVariant::MaxLength { keyword, value } => {
                    if let Some(first_keyword) = max_length_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "max_length"));
                    }

                    max_length_keyword = Some(keyword);
                    let max_length = value.base10_parse::<usize>()?;
                    output.max_length = Some(max_length);
                }
                SchemaParameterVariant::Example { keyword, value } => {
                    if let Some(first_keyword) = example_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "example"));
                    }

                    example_keyword = Some(keyword);
                    output.example = Some(value.value());
                }
            }
        }

        Ok(output)
    }
}

/// Check if the field is applicable for running validations
#[derive(PartialEq)]
pub enum IsSchemaFieldApplicableForValidation {
    /// Not applicable for running validation checks
    Invalid,
    /// Applicable for running validation checks
    Valid,
    /// Applicable for validation but field is optional - this is needed for generating validation code only if the value of the field is present
    ValidOptional,
}

/// From implementation for checking if the field type is applicable for running schema validations
impl From<&syn::Type> for IsSchemaFieldApplicableForValidation {
    fn from(ty: &syn::Type) -> Self {
        if let syn::Type::Path(type_path) = ty {
            if let Some(segment) = type_path.path.segments.last() {
                let ident = &segment.ident;
                if ident == "String" || ident == "Url" {
                    return Self::Valid;
                }

                if ident == "Option" {
                    if let syn::PathArguments::AngleBracketed(generic_args) = &segment.arguments {
                        if let Some(syn::GenericArgument::Type(syn::Type::Path(inner_path))) =
                            generic_args.args.first()
                        {
                            if let Some(inner_segment) = inner_path.path.segments.last() {
                                if inner_segment.ident == "String" || inner_segment.ident == "Url" {
                                    return Self::ValidOptional;
                                }
                            }
                        }
                    }
                }
            }
        }
        Self::Invalid
    }
}


// File: crates/subscriptions/Cargo.toml

[package]
name = "subscriptions"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

[dependencies]
async-trait = "0.1.88"
dyn-clone = "1.0.19"
error-stack = "0.4.1"
masking = { version = "0.1.0", path = "../masking" }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
time = { version = "0.3.41", features = ["serde", "std"] }

api_models = { version = "0.1.0", path = "../api_models", features = ["errors", "control_center_theme"] }
common_types = { version = "0.1.0", path = "../common_types" }
common_enums = { version = "0.1.0", path = "../common_enums" }
common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs", "metrics", "keymanager", "encryption_service"] }
hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false }
router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] }
scheduler = { version = "0.1.0", path = "../scheduler", default-features = false }
storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false }
hyperswitch_interfaces = { version = "0.1.0", path = "../hyperswitch_interfaces", default-features = false }

[lints]
workspace = true

[features]
default = ["v1"]
v1 = ["hyperswitch_domain_models/v1", "storage_impl/v1", "common_utils/v1", "scheduler/v1", "common_types/v1"]
v2 = ["common_utils/v2", "scheduler/v2", "common_types/v2"]


// File: crates/subscriptions/src/core.rs

use api_models::subscription::{self as subscription_types, SubscriptionResponse};
use common_enums::connector_enums;
use common_utils::id_type::GenerateId;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    api::ApplicationResponse, invoice::InvoiceUpdateRequest, platform::Platform,
    subscription::SubscriptionUpdate,
};

pub type RouterResponse<T> =
    Result<ApplicationResponse<T>, error_stack::Report<errors::ApiErrorResponse>>;
use api_models::enums::SubscriptionStatus;

use crate::{
    core::{
        billing_processor_handler::BillingHandler, invoice_handler::InvoiceHandler,
        subscription_handler::SubscriptionHandler,
    },
    state::SubscriptionState as SessionState,
};

pub mod billing_processor_handler;
pub mod errors;
pub mod invoice_handler;
pub mod payments_api_client;
pub mod subscription_handler;

pub const SUBSCRIPTION_CONNECTOR_ID: &str = "DefaultSubscriptionConnectorId";
pub const SUBSCRIPTION_PAYMENT_ID: &str = "DefaultSubscriptionPaymentId";

pub async fn create_subscription(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    request: subscription_types::CreateSubscriptionRequest,
) -> RouterResponse<SubscriptionResponse> {
    let subscription_id = common_utils::id_type::SubscriptionId::generate();

    let profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile")?;
    let _customer = SubscriptionHandler::find_customer(&state, &platform, &request.customer_id)
        .await
        .attach_printable("subscriptions: failed to find customer")?;
    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        profile.clone(),
    )
    .await?;

    let subscription_handler = SubscriptionHandler::new(&state, &platform);
    let mut subscription = subscription_handler
        .create_subscription_entry(
            subscription_id,
            &request.customer_id,
            billing_handler.connector_name,
            billing_handler.merchant_connector_id.clone(),
            request.merchant_reference_id.clone(),
            &profile.clone(),
            request.plan_id.clone(),
            Some(request.item_price_id.clone()),
        )
        .await
        .attach_printable("subscriptions: failed to create subscription entry")?;

    let estimate_request = subscription_types::EstimateSubscriptionQuery {
        plan_id: request.plan_id.clone(),
        item_price_id: request.item_price_id.clone(),
        coupon_code: None,
    };

    let estimate = billing_handler
        .get_subscription_estimate(&state, estimate_request)
        .await?;

    let invoice_handler = subscription.get_invoice_handler(profile.clone());
    let payment = invoice_handler
        .create_payment_with_confirm_false(
            subscription.handler.state,
            &request,
            estimate.total,
            estimate.currency,
        )
        .await
        .attach_printable("subscriptions: failed to create payment")?;

    let invoice = invoice_handler
        .create_invoice_entry(
            &state,
            billing_handler.merchant_connector_id,
            Some(payment.payment_id.clone()),
            estimate.total,
            estimate.currency,
            connector_enums::InvoiceStatus::InvoiceCreated,
            billing_handler.connector_name,
            None,
            None,
        )
        .await
        .attach_printable("subscriptions: failed to create invoice")?;

    subscription
        .update_subscription(SubscriptionUpdate::new(
            None,
            payment.payment_method_id.clone(),
            None,
            request.plan_id,
            Some(request.item_price_id),
        ))
        .await
        .attach_printable("subscriptions: failed to update subscription")?;

    let response = subscription.to_subscription_response(Some(payment), Some(&invoice))?;

    Ok(ApplicationResponse::Json(response))
}

pub async fn get_subscription_plans(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    query: subscription_types::GetPlansQuery,
) -> RouterResponse<Vec<subscription_types::GetPlansResponse>> {
    let profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile")?;

    let subscription_handler = SubscriptionHandler::new(&state, &platform);

    if let Some(client_secret) = query.client_secret {
        subscription_handler
            .find_and_validate_subscription(&client_secret.into())
            .await?
    };

    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        profile.clone(),
    )
    .await?;

    let get_plans_response = billing_handler
        .get_subscription_plans(&state, query.limit, query.offset)
        .await?;

    let mut response = Vec::new();

    for plan in &get_plans_response.list {
        let plan_price_response = billing_handler
            .get_subscription_plan_prices(&state, plan.subscription_provider_plan_id.clone())
            .await?;

        response.push(subscription_types::GetPlansResponse {
            plan_id: plan.subscription_provider_plan_id.clone(),
            name: plan.name.clone(),
            description: plan.description.clone(),
            price_id: plan_price_response
                .list
                .into_iter()
                .map(subscription_types::SubscriptionPlanPrices::from)
                .collect::<Vec<_>>(),
        });
    }
    Ok(ApplicationResponse::Json(response))
}
/// Creates and confirms a subscription in one operation.
pub async fn create_and_confirm_subscription(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    request: subscription_types::CreateAndConfirmSubscriptionRequest,
) -> RouterResponse<subscription_types::ConfirmSubscriptionResponse> {
    request
        .validate()
        .map_err(|message| errors::ApiErrorResponse::InvalidRequestData {
            message: message.to_string(),
        })?;

    let subscription_id = common_utils::id_type::SubscriptionId::generate();
    let profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile")?;
    let customer = SubscriptionHandler::find_customer(&state, &platform, &request.customer_id)
        .await
        .attach_printable("subscriptions: failed to find customer")?;

    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        profile.clone(),
    )
    .await?;
    let subscription_handler = SubscriptionHandler::new(&state, &platform);
    let mut subs_handler = subscription_handler
        .create_subscription_entry(
            subscription_id.clone(),
            &request.customer_id,
            billing_handler.connector_name,
            billing_handler.merchant_connector_id.clone(),
            request.merchant_reference_id.clone(),
            &profile.clone(),
            request.plan_id.clone(),
            Some(request.item_price_id.clone()),
        )
        .await
        .attach_printable("subscriptions: failed to create subscription entry")?;
    let invoice_handler = subs_handler.get_invoice_handler(profile.clone());

    let customer_create_response = billing_handler
        .create_customer_on_connector(
            &state,
            customer.clone(),
            request.customer_id.clone(),
            request.get_billing_address(),
            request
                .payment_details
                .payment_method_data
                .clone()
                .and_then(|data| data.payment_method_data),
        )
        .await?;
    let _customer_updated_response = SubscriptionHandler::update_connector_customer_id_in_customer(
        &state,
        &platform,
        &billing_handler.merchant_connector_id,
        &customer,
        customer_create_response,
    )
    .await
    .attach_printable("Failed to update customer with connector customer ID")?;

    let subscription_create_response = billing_handler
        .create_subscription_on_connector(
            &state,
            subs_handler.subscription.clone(),
            Some(request.item_price_id.clone()),
            request.get_billing_address(),
        )
        .await?;

    let invoice_details = subscription_create_response.invoice_details;
    let (amount, currency) =
        InvoiceHandler::get_amount_and_currency((None, None), invoice_details.clone());

    let payment_response = invoice_handler
        .create_and_confirm_payment(&state, &request, amount, currency)
        .await?;

    let invoice_entry = invoice_handler
        .create_invoice_entry(
            &state,
            profile.get_billing_processor_id()?,
            Some(payment_response.payment_id.clone()),
            amount,
            currency,
            invoice_details
                .clone()
                .and_then(|invoice| invoice.status)
                .unwrap_or(connector_enums::InvoiceStatus::InvoiceCreated),
            billing_handler.connector_name,
            None,
            invoice_details.clone().map(|invoice| invoice.id),
        )
        .await?;

    invoice_handler
        .create_invoice_sync_job(
            &state,
            &invoice_entry,
            invoice_details.clone().map(|details| details.id),
            billing_handler.connector_name,
        )
        .await?;

    subs_handler
        .update_subscription(SubscriptionUpdate::new(
            Some(
                subscription_create_response
                    .subscription_id
                    .get_string_repr()
                    .to_string(),
            ),
            payment_response.payment_method_id.clone(),
            Some(SubscriptionStatus::from(subscription_create_response.status).to_string()),
            request.plan_id,
            Some(request.item_price_id),
        ))
        .await?;

    let response = subs_handler.generate_response(
        &invoice_entry,
        &payment_response,
        subscription_create_response.status,
    )?;

    Ok(ApplicationResponse::Json(response))
}

pub async fn confirm_subscription(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    request: subscription_types::ConfirmSubscriptionRequest,
    subscription_id: common_utils::id_type::SubscriptionId,
) -> RouterResponse<subscription_types::ConfirmSubscriptionResponse> {
    // Validate request
    request
        .validate()
        .map_err(|message| errors::ApiErrorResponse::InvalidRequestData {
            message: message.to_string(),
        })?;
    // Find the subscription from database
    let profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile")?;

    let handler = SubscriptionHandler::new(&state, &platform);
    if let Some(client_secret) = request.client_secret.clone() {
        handler
            .find_and_validate_subscription(&client_secret.into())
            .await?
    };

    let mut subscription_entry = handler.find_subscription(subscription_id).await?;
    let invoice_handler = subscription_entry.get_invoice_handler(profile.clone());
    let invoice = invoice_handler
        .get_latest_invoice(&state)
        .await
        .attach_printable("subscriptions: failed to get latest invoice")?;
    let payment_response = invoice_handler
        .confirm_payment(
            &state,
            invoice
                .payment_intent_id
                .ok_or(errors::ApiErrorResponse::MissingRequiredField {
                    field_name: "payment_intent_id",
                })?,
            &request,
        )
        .await?;

    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        profile.clone(),
    )
    .await?;
    let customer = SubscriptionHandler::find_customer(
        &state,
        &platform,
        &subscription_entry.subscription.customer_id,
    )
    .await
    .attach_printable("subscriptions: failed to find customer")?;
    let invoice_handler = subscription_entry.get_invoice_handler(profile);
    let subscription = subscription_entry.subscription.clone();

    let customer_create_response = billing_handler
        .create_customer_on_connector(
            &state,
            customer.clone(),
            subscription.customer_id.clone(),
            payment_response.get_billing_address(),
            request
                .payment_details
                .payment_method_data
                .as_ref()
                .and_then(|data| data.payment_method_data.clone()),
        )
        .await?;
    let _customer_updated_response = SubscriptionHandler::update_connector_customer_id_in_customer(
        &state,
        &platform,
        &billing_handler.merchant_connector_id,
        &customer,
        customer_create_response,
    )
    .await
    .attach_printable("Failed to update customer with connector customer ID")?;

    let subscription_create_response = billing_handler
        .create_subscription_on_connector(
            &state,
            subscription.clone(),
            subscription.item_price_id.clone(),
            payment_response.get_billing_address(),
        )
        .await?;

    let invoice_details = subscription_create_response.invoice_details;
    let update_request = InvoiceUpdateRequest::update_payment_and_status(
        payment_response.payment_method_id.clone(),
        Some(payment_response.payment_id.clone()),
        invoice_details
            .clone()
            .and_then(|invoice| invoice.status)
            .unwrap_or(connector_enums::InvoiceStatus::InvoiceCreated),
        invoice_details.clone().map(|invoice| invoice.id),
    );
    let invoice_entry = invoice_handler
        .update_invoice(&state, invoice.id, update_request)
        .await?;

    invoice_handler
        .create_invoice_sync_job(
            &state,
            &invoice_entry,
            invoice_details.map(|invoice| invoice.id),
            billing_handler.connector_name,
        )
        .await?;

    subscription_entry
        .update_subscription(SubscriptionUpdate::new(
            Some(
                subscription_create_response
                    .subscription_id
                    .get_string_repr()
                    .to_string(),
            ),
            payment_response.payment_method_id.clone(),
            Some(SubscriptionStatus::from(subscription_create_response.status).to_string()),
            subscription.plan_id.clone(),
            subscription.item_price_id.clone(),
        ))
        .await?;

    let response = subscription_entry.generate_response(
        &invoice_entry,
        &payment_response,
        subscription_create_response.status,
    )?;

    Ok(ApplicationResponse::Json(response))
}

pub async fn get_subscription(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    subscription_id: common_utils::id_type::SubscriptionId,
) -> RouterResponse<SubscriptionResponse> {
    let _profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile in get_subscription")?;
    let handler = SubscriptionHandler::new(&state, &platform);
    let subscription = handler
        .find_subscription(subscription_id)
        .await
        .attach_printable("subscriptions: failed to get subscription entry in get_subscription")?;

    let response = subscription.to_subscription_response(None, None)?;

    Ok(ApplicationResponse::Json(response))
}

pub async fn get_estimate(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    query: subscription_types::EstimateSubscriptionQuery,
) -> RouterResponse<subscription_types::EstimateSubscriptionResponse> {
    let profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile in get_estimate")?;
    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        profile,
    )
    .await?;
    let estimate = billing_handler
        .get_subscription_estimate(&state, query)
        .await?;
    Ok(ApplicationResponse::Json(estimate.into()))
}

pub async fn pause_subscription(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    subscription_id: common_utils::id_type::SubscriptionId,
    request: subscription_types::PauseSubscriptionRequest,
) -> RouterResponse<subscription_types::PauseSubscriptionResponse> {
    let _profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile in pause_subscription")?;

    let handler = SubscriptionHandler::new(&state, &platform);
    let mut subscription_entry = handler.find_subscription(subscription_id).await?;

    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        _profile.clone(),
    )
    .await?;

    // Call the billing processor to pause the subscription
    let pause_response = billing_handler
        .pause_subscription_on_connector(&state, &subscription_entry.subscription, &request)
        .await?;
    let status = SubscriptionStatus::from(pause_response.status);
    // Update the subscription status in our database
    subscription_entry
        .update_subscription(SubscriptionUpdate::update_status(status.to_string()))
        .await?;

    let response = subscription_types::PauseSubscriptionResponse {
        id: subscription_entry.subscription.id.clone(),
        status,
        merchant_reference_id: subscription_entry
            .subscription
            .merchant_reference_id
            .clone(),
        profile_id: subscription_entry.subscription.profile_id.clone(),
        merchant_id: subscription_entry.subscription.merchant_id.clone(),
        customer_id: subscription_entry.subscription.customer_id.clone(),
        paused_at: pause_response.paused_at,
    };

    Ok(ApplicationResponse::Json(response))
}

pub async fn resume_subscription(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    subscription_id: common_utils::id_type::SubscriptionId,
    request: subscription_types::ResumeSubscriptionRequest,
) -> RouterResponse<subscription_types::ResumeSubscriptionResponse> {
    let _profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable(
            "subscriptions: failed to find business profile in resume_subscription",
        )?;

    let handler = SubscriptionHandler::new(&state, &platform);
    let mut subscription_entry = handler.find_subscription(subscription_id).await?;

    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        _profile.clone(),
    )
    .await?;

    // Call the billing processor to resume the subscription
    let resume_response = billing_handler
        .resume_subscription_on_connector(&state, &subscription_entry.subscription, &request)
        .await?;

    let status = SubscriptionStatus::from(resume_response.status);
    // Update the subscription status in our database
    subscription_entry
        .update_subscription(SubscriptionUpdate::update_status(status.to_string()))
        .await?;

    let response = subscription_types::ResumeSubscriptionResponse {
        id: subscription_entry.subscription.id.clone(),
        status,
        merchant_reference_id: subscription_entry
            .subscription
            .merchant_reference_id
            .clone(),
        profile_id: subscription_entry.subscription.profile_id.clone(),
        merchant_id: subscription_entry.subscription.merchant_id.clone(),
        customer_id: subscription_entry.subscription.customer_id.clone(),
        next_billing_at: resume_response.next_billing_at,
    };

    Ok(ApplicationResponse::Json(response))
}

pub async fn cancel_subscription(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    subscription_id: common_utils::id_type::SubscriptionId,
    request: subscription_types::CancelSubscriptionRequest,
) -> RouterResponse<subscription_types::CancelSubscriptionResponse> {
    let _profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable(
            "subscriptions: failed to find business profile in cancel_subscription",
        )?;

    let handler = SubscriptionHandler::new(&state, &platform);
    let mut subscription_entry = handler.find_subscription(subscription_id).await?;

    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        _profile.clone(),
    )
    .await?;

    // Call the billing processor to cancel the subscription
    let cancel_response = billing_handler
        .cancel_subscription_on_connector(&state, &subscription_entry.subscription, &request)
        .await?;

    let status = SubscriptionStatus::from(cancel_response.status);
    // Update the subscription status in our database
    subscription_entry
        .update_subscription(SubscriptionUpdate::update_status(status.to_string()))
        .await?;

    let response = subscription_types::CancelSubscriptionResponse {
        id: subscription_entry.subscription.id.clone(),
        status,
        merchant_reference_id: subscription_entry
            .subscription
            .merchant_reference_id
            .clone(),
        profile_id: subscription_entry.subscription.profile_id.clone(),
        merchant_id: subscription_entry.subscription.merchant_id.clone(),
        customer_id: subscription_entry.subscription.customer_id.clone(),
        cancelled_at: cancel_response.cancelled_at,
    };

    Ok(ApplicationResponse::Json(response))
}

pub async fn update_subscription(
    state: SessionState,
    platform: Platform,
    profile_id: common_utils::id_type::ProfileId,
    subscription_id: common_utils::id_type::SubscriptionId,
    request: subscription_types::UpdateSubscriptionRequest,
) -> RouterResponse<SubscriptionResponse> {
    let profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile in get_subscription")?;

    let handler = SubscriptionHandler::new(&state, &platform);
    let mut subscription_entry = handler.find_subscription(subscription_id).await?;

    let invoice_handler = subscription_entry.get_invoice_handler(profile.clone());
    let invoice = invoice_handler
        .get_latest_invoice(&state)
        .await
        .attach_printable("subscriptions: failed to get latest invoice")?;

    let subscription = subscription_entry.subscription.clone();

    subscription_entry
        .update_subscription(SubscriptionUpdate::new(
            None,
            None,
            None,
            Some(request.plan_id.clone()),
            Some(request.item_price_id.clone()),
        ))
        .await?;

    let billing_handler = BillingHandler::create(
        &state,
        platform.get_processor().get_account(),
        platform.get_processor().get_key_store(),
        profile.clone(),
    )
    .await?;

    let estimate_request = subscription_types::EstimateSubscriptionQuery {
        plan_id: Some(request.plan_id.clone()),
        item_price_id: request.item_price_id.clone(),
        coupon_code: None,
    };

    let estimate = billing_handler
        .get_subscription_estimate(&state, estimate_request)
        .await?;

    let update_request = InvoiceUpdateRequest::update_amount_and_currency(
        estimate.total,
        estimate.currency.to_string(),
    );

    let invoice_entry = invoice_handler
        .update_invoice(&state, invoice.id, update_request)
        .await?;

    let _payment_response = invoice_handler
        .update_payment(
            &state,
            estimate.total,
            estimate.currency,
            invoice_entry.payment_intent_id.ok_or(
                errors::ApiErrorResponse::MissingRequiredField {
                    field_name: "payment_intent_id",
                },
            )?,
        )
        .await?;

    Box::pin(get_subscription(
        state,
        platform,
        profile_id,
        subscription.id,
    ))
    .await
}


// File: crates/subscriptions/src/types.rs

//! Types module for subscription functionality
//!
//! This module contains type definitions and storage types for subscriptions

pub mod storage;

// Re-export subscription types from api_models for convenience
pub use api_models::subscription as subscription_types;


// File: crates/subscriptions/src/lib.rs

//! Subscription management crate for Hyperswitch
//!
//! This crate provides functionality for managing subscriptions, including:
//! - Subscription creation and management
//! - Invoice handling
//! - Billing processor integration
//! - Payment processing for subscriptions

#[cfg(feature = "v1")]
pub mod core;
pub mod helpers;
pub mod state;
pub mod types;
#[cfg(feature = "v1")]
pub mod workflows;

pub mod webhooks;

pub use core::*;

pub use types::*;


// File: crates/subscriptions/src/helpers.rs

pub use hyperswitch_domain_models::errors::api_error_response;

pub const X_PROFILE_ID: &str = "X-Profile-Id";
pub const X_TENANT_ID: &str = "x-tenant-id";
pub const X_MERCHANT_ID: &str = "X-Merchant-Id";
pub const X_INTERNAL_API_KEY: &str = "X-Internal-Api-Key";

pub trait ForeignFrom<F> {
    fn foreign_from(from: F) -> Self;
}

/// Trait for converting from one foreign type to another
pub trait ForeignTryFrom<F>: Sized {
    /// Custom error for conversion failure
    type Error;
    /// Convert from a foreign type to the current type and return an error if the conversion fails
    fn foreign_try_from(from: F) -> Result<Self, Self::Error>;
}

pub trait StorageErrorExt<T, E> {
    #[track_caller]
    fn to_not_found_response(self, not_found_response: E) -> error_stack::Result<T, E>;

    #[track_caller]
    fn to_duplicate_response(self, duplicate_response: E) -> error_stack::Result<T, E>;
}

impl<T> StorageErrorExt<T, api_error_response::ApiErrorResponse>
    for error_stack::Result<T, storage_impl::StorageError>
{
    #[track_caller]
    fn to_not_found_response(
        self,
        not_found_response: api_error_response::ApiErrorResponse,
    ) -> error_stack::Result<T, api_error_response::ApiErrorResponse> {
        self.map_err(|err| {
            let new_err = match err.current_context() {
                storage_impl::StorageError::ValueNotFound(_) => not_found_response,
                storage_impl::StorageError::CustomerRedacted => {
                    api_error_response::ApiErrorResponse::CustomerRedacted
                }
                _ => api_error_response::ApiErrorResponse::InternalServerError,
            };
            err.change_context(new_err)
        })
    }

    #[track_caller]
    fn to_duplicate_response(
        self,
        duplicate_response: api_error_response::ApiErrorResponse,
    ) -> error_stack::Result<T, api_error_response::ApiErrorResponse> {
        self.map_err(|err| {
            let new_err = match err.current_context() {
                storage_impl::StorageError::DuplicateValue { .. } => duplicate_response,
                _ => api_error_response::ApiErrorResponse::InternalServerError,
            };
            err.change_context(new_err)
        })
    }
}


// File: crates/subscriptions/src/state.rs

use common_utils::types::keymanager;
use hyperswitch_domain_models::{
    business_profile, configs as domain_configs, customer, invoice as invoice_domain, master_key,
    merchant_account, merchant_connector_account, merchant_key_store,
    subscription as subscription_domain,
};
use hyperswitch_interfaces::configs;
use router_env::RequestId;
use storage_impl::{errors, kv_router_store::KVRouterStore, DatabaseStore, MockDb, RouterStore};

#[async_trait::async_trait]
pub trait SubscriptionStorageInterface:
    Send
    + Sync
    + std::any::Any
    + dyn_clone::DynClone
    + master_key::MasterKeyInterface
    + scheduler::SchedulerInterface
    + subscription_domain::SubscriptionInterface<Error = errors::StorageError>
    + invoice_domain::InvoiceInterface<Error = errors::StorageError>
    + business_profile::ProfileInterface<Error = errors::StorageError>
    + domain_configs::ConfigInterface<Error = errors::StorageError>
    + customer::CustomerInterface<Error = errors::StorageError>
    + merchant_account::MerchantAccountInterface<Error = errors::StorageError>
    + merchant_key_store::MerchantKeyStoreInterface<Error = errors::StorageError>
    + merchant_connector_account::MerchantConnectorAccountInterface<Error = errors::StorageError>
    + 'static
{
}
dyn_clone::clone_trait_object!(SubscriptionStorageInterface);

#[async_trait::async_trait]
impl SubscriptionStorageInterface for MockDb {}

#[async_trait::async_trait]
impl<T: DatabaseStore + 'static> SubscriptionStorageInterface for RouterStore<T> where
    Self: scheduler::SchedulerInterface + master_key::MasterKeyInterface
{
}

#[async_trait::async_trait]
impl<T: DatabaseStore + 'static> SubscriptionStorageInterface for KVRouterStore<T> where
    Self: scheduler::SchedulerInterface + master_key::MasterKeyInterface
{
}

pub struct SubscriptionState {
    pub store: Box<dyn SubscriptionStorageInterface>,
    pub key_store: Option<merchant_key_store::MerchantKeyStore>,
    pub key_manager_state: keymanager::KeyManagerState,
    pub api_client: Box<dyn hyperswitch_interfaces::api_client::ApiClient>,
    pub conf: SubscriptionConfig,
    pub tenant: configs::Tenant,
    pub event_handler: Box<dyn hyperswitch_interfaces::events::EventHandlerInterface>,
    pub connector_converter: Box<dyn hyperswitch_interfaces::api_client::ConnectorConverter>,
}

#[derive(Clone)]
pub struct SubscriptionConfig {
    pub proxy: hyperswitch_interfaces::types::Proxy,
    pub internal_merchant_id_profile_id_auth: configs::InternalMerchantIdProfileIdAuthSettings,
    pub internal_services: configs::InternalServicesConfig,
    pub connectors: configs::Connectors,
}

impl From<&SubscriptionState> for keymanager::KeyManagerState {
    fn from(state: &SubscriptionState) -> Self {
        state.key_manager_state.clone()
    }
}

impl hyperswitch_interfaces::api_client::ApiClientWrapper for SubscriptionState {
    fn get_proxy(&self) -> hyperswitch_interfaces::types::Proxy {
        self.conf.proxy.clone()
    }

    fn get_api_client(&self) -> &dyn hyperswitch_interfaces::api_client::ApiClient {
        self.api_client.as_ref()
    }

    fn get_request_id(&self) -> Option<RequestId> {
        self.api_client.get_request_id()
    }
    fn get_request_id_str(&self) -> Option<String> {
        self.api_client
            .get_request_id()
            .map(|req_id| req_id.to_string())
    }

    fn get_tenant(&self) -> configs::Tenant {
        self.tenant.clone()
    }

    fn get_connectors(&self) -> configs::Connectors {
        self.conf.connectors.clone()
    }

    fn event_handler(&self) -> &dyn hyperswitch_interfaces::events::EventHandlerInterface {
        self.event_handler.as_ref()
    }
}


// File: crates/subscriptions/src/webhooks.rs

use std::str::FromStr;

use api_models::webhooks::WebhookResponseTracker;
use common_enums::{connector_enums::Connector, InvoiceStatus};
use common_utils::{consts, errors::CustomResult, generate_id};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    business_profile, errors::api_error_response as errors, invoice, merchant_connector_account,
    platform,
};
use hyperswitch_interfaces::{
    api::ConnectorCommon, connector_integration_interface, errors::ConnectorError,
    webhooks::IncomingWebhook,
};
use router_env::{instrument, logger, tracing};

use crate::state::SubscriptionState as SessionState;
#[cfg(feature = "v1")]
use crate::subscription_handler::SubscriptionHandler;

#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn incoming_webhook_flow(
    state: SessionState,
    platform: platform::Platform,
    business_profile: business_profile::Profile,
    _webhook_details: api_models::webhooks::IncomingWebhookDetails,
    source_verified: bool,
    connector_enum: &connector_integration_interface::ConnectorEnum,
    request_details: &hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails<'_>,
    event_type: api_models::webhooks::IncomingWebhookEvent,
    merchant_connector_account: merchant_connector_account::MerchantConnectorAccount,
) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> {
    let billing_connector_mca_id = merchant_connector_account.merchant_connector_id.clone();
    // Only process invoice_generated events for MIT payments
    if event_type != api_models::webhooks::IncomingWebhookEvent::InvoiceGenerated {
        return Ok(WebhookResponseTracker::NoEffect);
    }

    if !source_verified {
        logger::error!("Webhook source verification failed for subscription webhook flow");
        return Err(report!(
            errors::ApiErrorResponse::WebhookAuthenticationFailed
        ));
    }

    let connector_name = connector_enum.id().to_string();

    let connector = Connector::from_str(&connector_name)
        .change_context(ConnectorError::InvalidConnectorName)
        .change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven)
        .attach_printable_lazy(|| format!("unable to parse connector name {connector_name}"))?;

    let mit_payment_data = connector_enum
        .get_subscription_mit_payment_data(request_details)
        .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
        .attach_printable("Failed to extract MIT payment data from subscription webhook")?;

    let profile_id = business_profile.get_id().clone();

    let profile = SubscriptionHandler::find_business_profile(&state, &platform, &profile_id)
        .await
        .attach_printable("subscriptions: failed to find business profile in get_subscription")?;

    let handler = SubscriptionHandler::new(&state, &platform);

    let subscription_id = mit_payment_data.subscription_id.clone();

    let subscription_with_handler = handler
        .find_subscription(subscription_id.clone())
        .await
        .attach_printable("subscriptions: failed to get subscription entry in get_subscription")?;

    let invoice_handler = subscription_with_handler.get_invoice_handler(profile.clone());
    let invoice = invoice_handler
        .find_invoice_by_subscription_id_connector_invoice_id(
            &state,
            subscription_id,
            mit_payment_data.invoice_id.clone(),
        )
        .await
        .attach_printable(
            "subscriptions: failed to get invoice by subscription id and connector invoice id",
        )?;
    if let Some(invoice) = invoice {
        // During CIT payment we would have already created invoice entry with status as PaymentPending or Paid.
        // So we skip incoming webhook for the already processed invoice
        if invoice.status != InvoiceStatus::InvoiceCreated {
            logger::info!("Invoice is already being processed, skipping MIT payment creation");
            return Ok(WebhookResponseTracker::NoEffect);
        }
    }

    let payment_method_id = subscription_with_handler
        .subscription
        .payment_method_id
        .clone()
        .ok_or(errors::ApiErrorResponse::GenericNotFoundError {
            message: "No payment method found for subscription".to_string(),
        })
        .attach_printable("No payment method found for subscription")?;

    logger::info!("Payment method ID found: {}", payment_method_id);

    let payment_id = generate_id(consts::ID_LENGTH, "pay");
    let payment_id = common_utils::id_type::PaymentId::wrap(payment_id).change_context(
        errors::ApiErrorResponse::InvalidDataValue {
            field_name: "payment_id",
        },
    )?;

    // Multiple MIT payments for the same invoice_generated event is avoided by having the unique constraint on (subscription_id, connector_invoice_id) in the invoices table
    let invoice_entry = invoice_handler
        .create_invoice_entry(
            &state,
            billing_connector_mca_id.clone(),
            Some(payment_id),
            mit_payment_data.amount_due,
            mit_payment_data.currency_code,
            InvoiceStatus::PaymentPending,
            connector,
            None,
            Some(mit_payment_data.invoice_id.clone()),
        )
        .await?;

    // Create a sync job for the invoice with generated payment_id before initiating MIT payment creation.
    // This ensures that if payment creation call fails, the sync job can still retrieve the payment status
    invoice_handler
        .create_invoice_sync_job(
            &state,
            &invoice_entry,
            Some(mit_payment_data.invoice_id.clone()),
            connector,
        )
        .await?;

    let payment_response = invoice_handler
        .create_mit_payment(
            &state,
            mit_payment_data.amount_due,
            mit_payment_data.currency_code,
            &payment_method_id.clone(),
        )
        .await?;

    let update_request = invoice::InvoiceUpdateRequest::update_payment_and_status(
        payment_response.payment_method_id,
        Some(payment_response.payment_id.clone()),
        InvoiceStatus::from(payment_response.status),
        Some(mit_payment_data.invoice_id.clone()),
    );

    let _updated_invoice = invoice_handler
        .update_invoice(&state, invoice_entry.id.clone(), update_request)
        .await?;

    Ok(WebhookResponseTracker::NoEffect)
}


// File: crates/subscriptions/src/workflows.rs

//! Workflows module for subscription functionality
//!
//! This module contains workflow definitions for subscription-related operations

pub mod invoice_sync;

// Re-export workflow types for easier access
pub use invoice_sync::*;


// File: crates/subscriptions/src/types/storage.rs

pub mod invoice_sync;


// File: crates/subscriptions/src/types/storage/invoice_sync.rs

use api_models::enums as api_enums;
use common_utils::id_type;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InvoiceSyncTrackingData {
    pub subscription_id: id_type::SubscriptionId,
    pub invoice_id: id_type::InvoiceId,
    pub merchant_id: id_type::MerchantId,
    pub profile_id: id_type::ProfileId,
    pub customer_id: id_type::CustomerId,
    // connector_invoice_id is optional because in some cases (Trial/Future), the invoice might not have been created in the connector yet.
    pub connector_invoice_id: Option<id_type::InvoiceId>,
    pub connector_name: api_enums::Connector, // The connector to which the invoice belongs
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InvoiceSyncRequest {
    pub subscription_id: id_type::SubscriptionId,
    pub invoice_id: id_type::InvoiceId,
    pub merchant_id: id_type::MerchantId,
    pub profile_id: id_type::ProfileId,
    pub customer_id: id_type::CustomerId,
    pub connector_invoice_id: Option<id_type::InvoiceId>,
    pub connector_name: api_enums::Connector,
}

impl From<InvoiceSyncRequest> for InvoiceSyncTrackingData {
    fn from(item: InvoiceSyncRequest) -> Self {
        Self {
            subscription_id: item.subscription_id,
            invoice_id: item.invoice_id,
            merchant_id: item.merchant_id,
            profile_id: item.profile_id,
            customer_id: item.customer_id,
            connector_invoice_id: item.connector_invoice_id,
            connector_name: item.connector_name,
        }
    }
}

impl InvoiceSyncRequest {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        subscription_id: id_type::SubscriptionId,
        invoice_id: id_type::InvoiceId,
        merchant_id: id_type::MerchantId,
        profile_id: id_type::ProfileId,
        customer_id: id_type::CustomerId,
        connector_invoice_id: Option<id_type::InvoiceId>,
        connector_name: api_enums::Connector,
    ) -> Self {
        Self {
            subscription_id,
            invoice_id,
            merchant_id,
            profile_id,
            customer_id,
            connector_invoice_id,
            connector_name,
        }
    }
}

impl InvoiceSyncTrackingData {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        subscription_id: id_type::SubscriptionId,
        invoice_id: id_type::InvoiceId,
        merchant_id: id_type::MerchantId,
        profile_id: id_type::ProfileId,
        customer_id: id_type::CustomerId,
        connector_invoice_id: Option<id_type::InvoiceId>,
        connector_name: api_enums::Connector,
    ) -> Self {
        Self {
            subscription_id,
            invoice_id,
            merchant_id,
            profile_id,
            customer_id,
            connector_invoice_id,
            connector_name,
        }
    }
}

#[derive(Debug, Clone)]
pub enum InvoiceSyncPaymentStatus {
    PaymentSucceeded,
    PaymentProcessing,
    PaymentFailed,
}

impl From<common_enums::IntentStatus> for InvoiceSyncPaymentStatus {
    fn from(value: common_enums::IntentStatus) -> Self {
        match value {
            common_enums::IntentStatus::Succeeded => Self::PaymentSucceeded,
            common_enums::IntentStatus::Processing
            | common_enums::IntentStatus::RequiresCustomerAction
            | common_enums::IntentStatus::RequiresConfirmation
            | common_enums::IntentStatus::RequiresPaymentMethod => Self::PaymentProcessing,
            _ => Self::PaymentFailed,
        }
    }
}

impl From<InvoiceSyncPaymentStatus> for common_enums::connector_enums::InvoiceStatus {
    fn from(value: InvoiceSyncPaymentStatus) -> Self {
        match value {
            InvoiceSyncPaymentStatus::PaymentSucceeded => Self::InvoicePaid,
            InvoiceSyncPaymentStatus::PaymentProcessing => Self::PaymentPending,
            InvoiceSyncPaymentStatus::PaymentFailed => Self::PaymentFailed,
        }
    }
}


// File: crates/subscriptions/src/core/subscription_handler.rs

use std::str::FromStr;

use api_models::{
    enums as api_enums,
    subscription::{self as subscription_types, SubscriptionResponse},
};
use common_enums::connector_enums;
use common_utils::{consts, ext_traits::OptionExt};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    customer, merchant_connector_account,
    platform::Platform,
    router_response_types::{self, subscriptions as subscription_response_types},
    subscription::{Subscription, SubscriptionStatus},
};
use masking::Secret;

use super::errors;
use crate::{
    core::invoice_handler::InvoiceHandler,
    errors::CustomResult,
    helpers::{ForeignTryFrom, StorageErrorExt},
    state::SubscriptionState as SessionState,
};

pub struct SubscriptionHandler<'a> {
    pub state: &'a SessionState,
    pub platform: &'a Platform,
}

impl<'a> SubscriptionHandler<'a> {
    pub fn new(state: &'a SessionState, platform: &'a Platform) -> Self {
        Self { state, platform }
    }

    #[allow(clippy::too_many_arguments)]
    /// Helper function to create a subscription entry in the database.
    pub async fn create_subscription_entry(
        &self,
        subscription_id: common_utils::id_type::SubscriptionId,
        customer_id: &common_utils::id_type::CustomerId,
        billing_processor: connector_enums::Connector,
        merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
        merchant_reference_id: Option<String>,
        profile: &hyperswitch_domain_models::business_profile::Profile,
        plan_id: Option<String>,
        item_price_id: Option<String>,
    ) -> errors::SubscriptionResult<SubscriptionWithHandler<'_>> {
        let store = self.state.store.clone();
        let db = store.as_ref();

        let mut subscription = Subscription {
            id: subscription_id,
            status: SubscriptionStatus::Created.to_string(),
            billing_processor: Some(billing_processor.to_string()),
            payment_method_id: None,
            merchant_connector_id: Some(merchant_connector_id),
            client_secret: None,
            connector_subscription_id: None,
            merchant_id: self.platform.get_processor().get_account().get_id().clone(),
            customer_id: customer_id.clone(),
            metadata: None,
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            profile_id: profile.get_id().clone(),
            merchant_reference_id,
            plan_id,
            item_price_id,
        };

        subscription.generate_and_set_client_secret();

        let merchant_key_store = self.platform.get_processor().get_key_store();
        let new_subscription = db
            .insert_subscription_entry(merchant_key_store, subscription)
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("subscriptions: unable to insert subscription entry to database")?;

        Ok(SubscriptionWithHandler {
            handler: self,
            subscription: new_subscription,
            merchant_account: self.platform.get_processor().get_account().clone(),
        })
    }

    /// Helper function to find and validate customer.
    pub async fn find_customer(
        state: &SessionState,
        platform: &Platform,
        customer_id: &common_utils::id_type::CustomerId,
    ) -> errors::SubscriptionResult<customer::Customer> {
        let merchant_key_store = platform.get_processor().get_key_store();
        let merchant_id = platform.get_processor().get_account().get_id();

        state
            .store
            .find_customer_by_customer_id_merchant_id(
                customer_id,
                merchant_id,
                merchant_key_store,
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::CustomerNotFound)
            .attach_printable("subscriptions: unable to fetch customer from database")
    }
    pub async fn update_connector_customer_id_in_customer(
        state: &SessionState,
        platform: &Platform,
        merchant_connector_id: &common_utils::id_type::MerchantConnectorAccountId,
        customer: &customer::Customer,
        customer_create_response: Option<router_response_types::ConnectorCustomerResponseData>,
    ) -> errors::SubscriptionResult<customer::Customer> {
        match customer_create_response {
            Some(customer_response) => {
                match customer::update_connector_customer_in_customers(
                    merchant_connector_id.get_string_repr(),
                    Some(customer),
                    Some(customer_response.connector_customer_id),
                )
                .await
                {
                    Some(customer_update) => {
                        Self::update_customer(state, platform, customer.clone(), customer_update)
                            .await
                            .attach_printable(
                                "Failed to update customer with connector customer ID",
                            )
                    }
                    None => Ok(customer.clone()),
                }
            }
            None => Ok(customer.clone()),
        }
    }

    pub async fn update_customer(
        state: &SessionState,
        platform: &Platform,
        customer: customer::Customer,
        customer_update: customer::CustomerUpdate,
    ) -> errors::SubscriptionResult<customer::Customer> {
        let merchant_key_store = platform.get_processor().get_key_store();
        let merchant_id = platform.get_processor().get_account().get_id();
        let db = state.store.as_ref();

        let updated_customer = db
            .update_customer_by_customer_id_merchant_id(
                customer.customer_id.clone(),
                merchant_id.clone(),
                customer,
                customer_update,
                merchant_key_store,
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("subscriptions: unable to update customer entry in database")?;

        Ok(updated_customer)
    }

    /// Helper function to find business profile.
    pub async fn find_business_profile(
        state: &SessionState,
        platform: &Platform,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> errors::SubscriptionResult<hyperswitch_domain_models::business_profile::Profile> {
        let merchant_key_store = platform.get_processor().get_key_store();

        state
            .store
            .find_business_profile_by_profile_id(merchant_key_store, profile_id)
            .await
            .change_context(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_string(),
            })
    }

    pub async fn find_and_validate_subscription(
        &self,
        client_secret: &hyperswitch_domain_models::subscription::ClientSecret,
    ) -> errors::SubscriptionResult<()> {
        let subscription_id = client_secret.get_subscription_id()?;
        let key_store = self.platform.get_processor().get_key_store();

        let subscription = self
            .state
            .store
            .find_by_merchant_id_subscription_id(
                key_store,
                self.platform.get_processor().get_account().get_id(),
                subscription_id.to_string(),
            )
            .await
            .change_context(errors::ApiErrorResponse::GenericNotFoundError {
                message: format!("Subscription not found for id: {subscription_id}"),
            })
            .attach_printable("Unable to find subscription")?;

        self.validate_client_secret(client_secret, &subscription)?;

        Ok(())
    }

    pub fn validate_client_secret(
        &self,
        client_secret: &hyperswitch_domain_models::subscription::ClientSecret,
        subscription: &Subscription,
    ) -> errors::SubscriptionResult<()> {
        let stored_client_secret = subscription
            .client_secret
            .clone()
            .get_required_value("client_secret")
            .change_context(errors::ApiErrorResponse::MissingRequiredField {
                field_name: "client_secret",
            })
            .attach_printable("client secret not found in db")?;

        if client_secret.to_string() != stored_client_secret {
            Err(errors::ApiErrorResponse::ClientSecretInvalid.into())
        } else {
            let current_timestamp = common_utils::date_time::now();
            let session_expiry = subscription
                .created_at
                .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY));

            if current_timestamp > session_expiry {
                Err(errors::ApiErrorResponse::ClientSecretExpired.into())
            } else {
                Ok(())
            }
        }
    }

    pub async fn find_subscription(
        &self,
        subscription_id: common_utils::id_type::SubscriptionId,
    ) -> errors::SubscriptionResult<SubscriptionWithHandler<'_>> {
        let subscription = self
            .state
            .store
            .find_by_merchant_id_subscription_id(
                self.platform.get_processor().get_key_store(),
                self.platform.get_processor().get_account().get_id(),
                subscription_id.get_string_repr().to_string().clone(),
            )
            .await
            .change_context(errors::ApiErrorResponse::GenericNotFoundError {
                message: format!(
                    "subscription not found for id: {}",
                    subscription_id.get_string_repr()
                ),
            })?;

        Ok(SubscriptionWithHandler {
            handler: self,
            subscription,
            merchant_account: self.platform.get_processor().get_account().clone(),
        })
    }
}
pub struct SubscriptionWithHandler<'a> {
    pub handler: &'a SubscriptionHandler<'a>,
    pub subscription: Subscription,
    pub merchant_account: hyperswitch_domain_models::merchant_account::MerchantAccount,
}

impl SubscriptionWithHandler<'_> {
    pub fn generate_response(
        &self,
        invoice: &hyperswitch_domain_models::invoice::Invoice,
        payment_response: &subscription_types::PaymentResponseData,
        status: subscription_response_types::SubscriptionStatus,
    ) -> errors::SubscriptionResult<subscription_types::ConfirmSubscriptionResponse> {
        Ok(subscription_types::ConfirmSubscriptionResponse {
            id: self.subscription.id.clone(),
            merchant_reference_id: self.subscription.merchant_reference_id.clone(),
            status: common_enums::SubscriptionStatus::from(status),
            plan_id: self.subscription.plan_id.clone(),
            profile_id: self.subscription.profile_id.to_owned(),
            payment: Some(payment_response.clone()),
            customer_id: Some(self.subscription.customer_id.clone()),
            item_price_id: self.subscription.item_price_id.clone(),
            coupon: None,
            billing_processor_subscription_id: self.subscription.connector_subscription_id.clone(),
            invoice: Some(subscription_types::Invoice::foreign_try_from(invoice)?),
        })
    }

    pub fn to_subscription_response(
        &self,
        payment: Option<subscription_types::PaymentResponseData>,
        invoice: Option<&hyperswitch_domain_models::invoice::Invoice>,
    ) -> errors::SubscriptionResult<SubscriptionResponse> {
        Ok(SubscriptionResponse::new(
            self.subscription.id.clone(),
            self.subscription.merchant_reference_id.clone(),
            common_enums::SubscriptionStatus::from_str(&self.subscription.status)
                .unwrap_or(common_enums::SubscriptionStatus::Created),
            self.subscription.plan_id.clone(),
            self.subscription.item_price_id.clone(),
            self.subscription.profile_id.to_owned(),
            self.subscription.merchant_id.to_owned(),
            self.subscription.client_secret.clone().map(Secret::new),
            self.subscription.customer_id.clone(),
            payment,
            invoice
                .map(
                    |invoice| -> errors::SubscriptionResult<subscription_types::Invoice> {
                        subscription_types::Invoice::foreign_try_from(invoice)
                    },
                )
                .transpose()?,
        ))
    }

    pub async fn update_subscription(
        &mut self,
        subscription_update: hyperswitch_domain_models::subscription::SubscriptionUpdate,
    ) -> errors::SubscriptionResult<()> {
        let db = self.handler.state.store.as_ref();
        let updated_subscription = db
            .update_subscription_entry(
                self.handler.platform.get_processor().get_key_store(),
                self.handler.platform.get_processor().get_account().get_id(),
                self.subscription.id.get_string_repr().to_string(),
                subscription_update,
            )
            .await
            .change_context(errors::ApiErrorResponse::SubscriptionError {
                operation: "Subscription Update".to_string(),
            })
            .attach_printable("subscriptions: unable to update subscription entry in database")?;

        self.subscription = updated_subscription;

        Ok(())
    }

    pub fn get_invoice_handler(
        &self,
        profile: hyperswitch_domain_models::business_profile::Profile,
    ) -> InvoiceHandler {
        InvoiceHandler {
            subscription: self.subscription.clone(),
            merchant_account: self.merchant_account.clone(),
            profile,
            merchant_key_store: self
                .handler
                .platform
                .get_processor()
                .get_key_store()
                .clone(),
        }
    }
    pub async fn get_mca(
        &mut self,
        connector_name: &str,
    ) -> CustomResult<merchant_connector_account::MerchantConnectorAccount, errors::ApiErrorResponse>
    {
        let db = self.handler.state.store.as_ref();

        match &self.subscription.merchant_connector_id {
            Some(merchant_connector_id) => {
                #[cfg(feature = "v1")]
                {
                    db.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
                        self.handler.platform.get_processor().get_account().get_id(),
                        merchant_connector_id,
                        self.handler.platform.get_processor().get_key_store(),
                    )
                    .await
                    .to_not_found_response(
                        errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                            id: merchant_connector_id.get_string_repr().to_string(),
                        },
                    )
                }
            }
            None => {
                // Fallback to profile-based lookup when merchant_connector_id is not set
                #[cfg(feature = "v1")]
                {
                    db.find_merchant_connector_account_by_profile_id_connector_name(
                        &self.subscription.profile_id,
                        connector_name,
                        self.handler.platform.get_processor().get_key_store(),
                    )
                    .await
                    .to_not_found_response(
                        errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                            id: format!(
                                "profile_id {} and connector_name {connector_name}",
                                self.subscription.profile_id.get_string_repr()
                            ),
                        },
                    )
                }
            }
        }
    }
}

impl ForeignTryFrom<&hyperswitch_domain_models::invoice::Invoice> for subscription_types::Invoice {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn foreign_try_from(
        invoice: &hyperswitch_domain_models::invoice::Invoice,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            id: invoice.id.clone(),
            subscription_id: invoice.subscription_id.clone(),
            merchant_id: invoice.merchant_id.clone(),
            profile_id: invoice.profile_id.clone(),
            merchant_connector_id: invoice.merchant_connector_id.clone(),
            payment_intent_id: invoice.payment_intent_id.clone(),
            payment_method_id: invoice.payment_method_id.clone(),
            customer_id: invoice.customer_id.clone(),
            amount: invoice.amount,
            currency: api_enums::Currency::from_str(invoice.currency.as_str())
                .change_context(errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "currency",
                })
                .attach_printable(format!(
                    "unable to parse currency name {currency:?}",
                    currency = invoice.currency
                ))?,
            status: invoice.status.clone(),
            billing_processor_invoice_id: invoice
                .connector_invoice_id
                .as_ref()
                .map(|id| id.get_string_repr().to_string()),
        })
    }
}


// File: crates/subscriptions/src/core/billing_processor_handler.rs

use std::str::FromStr;

use api_models::subscription as subscription_types;
use common_enums::{connector_enums, CallConnectorAction};
use common_utils::{ext_traits::ValueExt, pii};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    errors::api_error_response as errors,
    router_data_v2::flow_common_types::{
        GetSubscriptionEstimateData, GetSubscriptionPlanPricesData, GetSubscriptionPlansData,
        InvoiceRecordBackData, SubscriptionCancelData, SubscriptionCreateData,
        SubscriptionCustomerData, SubscriptionPauseData, SubscriptionResumeData,
    },
    router_request_types::{
        revenue_recovery::InvoiceRecordBackRequest, subscriptions as subscription_request_types,
        ConnectorCustomerData,
    },
    router_response_types::{
        revenue_recovery::InvoiceRecordBackResponse, subscriptions as subscription_response_types,
        ConnectorCustomerResponseData, PaymentsResponseData,
    },
};
use hyperswitch_interfaces::{
    api_client, configs::MerchantConnectorAccountType, connector_integration_interface,
};

use crate::{errors::SubscriptionResult, state::SubscriptionState as SessionState};

pub struct BillingHandler {
    pub auth_type: hyperswitch_domain_models::router_data::ConnectorAuthType,
    pub connector_name: connector_enums::Connector,
    pub connector_enum: connector_integration_interface::ConnectorEnum,
    pub connector_params: hyperswitch_domain_models::connector_endpoints::ConnectorParams,
    pub connector_metadata: Option<pii::SecretSerdeValue>,
    pub merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
}

#[allow(clippy::todo)]
impl BillingHandler {
    pub async fn create(
        state: &SessionState,
        merchant_account: &hyperswitch_domain_models::merchant_account::MerchantAccount,
        key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
        profile: hyperswitch_domain_models::business_profile::Profile,
    ) -> SubscriptionResult<Self> {
        let merchant_connector_id = profile.get_billing_processor_id()?;

        let billing_processor_mca = state
            .store
            .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
                merchant_account.get_id(),
                &merchant_connector_id,
                key_store,
            )
            .await
            .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                id: merchant_connector_id.get_string_repr().to_string(),
            })?;

        let connector_name = billing_processor_mca.connector_name.clone();

        let auth_type: hyperswitch_domain_models::router_data::ConnectorAuthType =
            MerchantConnectorAccountType::DbVal(Box::new(billing_processor_mca.clone()))
                .get_connector_account_details()
                .parse_value("ConnectorAuthType")
                .change_context(errors::ApiErrorResponse::InvalidDataFormat {
                    field_name: "connector_account_details".to_string(),
                    expected_format: "auth_type and api_key".to_string(),
                })?;

        let connector_enum = state
            .connector_converter
            .get_connector_enum_by_name(&connector_name)
            .change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven)
            .attach_printable(
                "invalid connector name received in billing merchant connector account",
            )?;

        let connector_data = connector_enums::Connector::from_str(connector_name.as_str())
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable(format!("unable to parse connector name {connector_name:?}"))?;

        let connector_params =
            hyperswitch_domain_models::connector_endpoints::Connectors::get_connector_params(
                &state.conf.connectors,
                connector_data,
            )
            .change_context(errors::ApiErrorResponse::ConfigNotFound)
            .attach_printable(format!(
                "cannot find connector params for this connector {connector_name} in this flow",
            ))?;

        Ok(Self {
            auth_type,
            connector_enum,
            connector_name: connector_data,
            connector_params,
            connector_metadata: billing_processor_mca.metadata.clone(),
            merchant_connector_id,
        })
    }

    pub async fn create_customer_on_connector(
        &self,
        state: &SessionState,
        customer: hyperswitch_domain_models::customer::Customer,
        customer_id: common_utils::id_type::CustomerId,
        billing_address: Option<api_models::payments::Address>,
        payment_method_data: Option<api_models::payments::PaymentMethodData>,
    ) -> SubscriptionResult<Option<ConnectorCustomerResponseData>> {
        let connector_customer_map = customer.get_connector_customer_map();
        if connector_customer_map.contains_key(&self.merchant_connector_id) {
            // Customer already exists on the connector, no need to create again
            return Ok(None);
        }
        let customer_req = ConnectorCustomerData {
            email: customer.email.clone().map(pii::Email::from),
            payment_method_data: payment_method_data.clone().map(|pmd| pmd.into()),
            description: None,
            phone: None,
            name: None,
            preprocessing_id: None,
            split_payments: None,
            setup_future_usage: None,
            customer_acceptance: None,
            customer_id: Some(customer_id.clone()),
            billing_address: billing_address
                .as_ref()
                .and_then(|add| add.address.clone())
                .and_then(|addr| addr.into()),
        };
        let router_data = self.build_router_data(
            state,
            customer_req,
            SubscriptionCustomerData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;
        let connector_integration = self.connector_enum.get_connector_integration();

        let response = Box::pin(self.call_connector(
            state,
            router_data,
            "create customer on connector",
            connector_integration,
        ))
        .await?;

        match response {
            Ok(response_data) => match response_data {
                PaymentsResponseData::ConnectorCustomerResponse(customer_response) => {
                    Ok(Some(customer_response))
                }
                _ => Err(errors::ApiErrorResponse::SubscriptionError {
                    operation: "Subscription Customer Create".to_string(),
                }
                .into()),
            },
            Err(err) => Err(errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: self.connector_name.to_string(),
                status_code: err.status_code,
                reason: err.reason,
            }
            .into()),
        }
    }

    pub async fn create_subscription_on_connector(
        &self,
        state: &SessionState,
        subscription: hyperswitch_domain_models::subscription::Subscription,
        item_price_id: Option<String>,
        billing_address: Option<api_models::payments::Address>,
    ) -> SubscriptionResult<subscription_response_types::SubscriptionCreateResponse> {
        let subscription_item = subscription_request_types::SubscriptionItem {
            item_price_id: item_price_id.ok_or(errors::ApiErrorResponse::MissingRequiredField {
                field_name: "item_price_id",
            })?,
            quantity: Some(1),
        };
        let subscription_req = subscription_request_types::SubscriptionCreateRequest {
            subscription_id: subscription.id.to_owned(),
            customer_id: subscription.customer_id.to_owned(),
            subscription_items: vec![subscription_item],
            billing_address: billing_address.ok_or(
                errors::ApiErrorResponse::MissingRequiredField {
                    field_name: "billing",
                },
            )?,
            auto_collection: subscription_request_types::SubscriptionAutoCollection::Off,
            connector_params: self.connector_params.clone(),
        };

        let router_data = self.build_router_data(
            state,
            subscription_req,
            SubscriptionCreateData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;
        let connector_integration = self.connector_enum.get_connector_integration();

        let response = self
            .call_connector(
                state,
                router_data,
                "create subscription on connector",
                connector_integration,
            )
            .await?;
        self.handle_connector_response(response)
    }
    #[allow(clippy::too_many_arguments)]
    pub async fn record_back_to_billing_processor(
        &self,
        state: &SessionState,
        invoice_id: common_utils::id_type::InvoiceId,
        payment_id: common_utils::id_type::PaymentId,
        payment_status: common_enums::AttemptStatus,
        amount: common_utils::types::MinorUnit,
        currency: common_enums::Currency,
        payment_method_type: Option<common_enums::PaymentMethodType>,
    ) -> SubscriptionResult<InvoiceRecordBackResponse> {
        let invoice_record_back_req = InvoiceRecordBackRequest {
            amount,
            currency,
            payment_method_type,
            attempt_status: payment_status,
            merchant_reference_id: common_utils::id_type::PaymentReferenceId::from_str(
                invoice_id.get_string_repr(),
            )
            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "invoice_id",
            })?,
            connector_params: self.connector_params.clone(),
            connector_transaction_id: Some(common_utils::types::ConnectorTransactionId::TxnId(
                payment_id.get_string_repr().to_string(),
            )),
        };

        let router_data = self.build_router_data(
            state,
            invoice_record_back_req,
            InvoiceRecordBackData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;
        let connector_integration = self.connector_enum.get_connector_integration();

        let response = self
            .call_connector(
                state,
                router_data,
                "invoice record back",
                connector_integration,
            )
            .await?;
        self.handle_connector_response(response)
    }

    pub async fn get_subscription_estimate(
        &self,
        state: &SessionState,
        estimate_request: subscription_types::EstimateSubscriptionQuery,
    ) -> SubscriptionResult<subscription_response_types::GetSubscriptionEstimateResponse> {
        let estimate_req = subscription_request_types::GetSubscriptionEstimateRequest {
            price_id: estimate_request.item_price_id.clone(),
        };

        let router_data = self.build_router_data(
            state,
            estimate_req,
            GetSubscriptionEstimateData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;
        let connector_integration = self.connector_enum.get_connector_integration();

        let response = self
            .call_connector(
                state,
                router_data,
                "get subscription estimate from connector",
                connector_integration,
            )
            .await?;
        self.handle_connector_response(response)
    }

    pub async fn get_subscription_plans(
        &self,
        state: &SessionState,
        limit: Option<u32>,
        offset: Option<u32>,
    ) -> SubscriptionResult<subscription_response_types::GetSubscriptionPlansResponse> {
        let get_plans_request =
            subscription_request_types::GetSubscriptionPlansRequest::new(limit, offset);

        let router_data = self.build_router_data(
            state,
            get_plans_request,
            GetSubscriptionPlansData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;

        let connector_integration = self.connector_enum.get_connector_integration();

        let response = self
            .call_connector(
                state,
                router_data,
                "get subscription plans",
                connector_integration,
            )
            .await?;
        self.handle_connector_response(response)
    }

    pub async fn get_subscription_plan_prices(
        &self,
        state: &SessionState,
        plan_price_id: String,
    ) -> SubscriptionResult<subscription_response_types::GetSubscriptionPlanPricesResponse> {
        let get_plan_prices_request =
            subscription_request_types::GetSubscriptionPlanPricesRequest { plan_price_id };

        let router_data = self.build_router_data(
            state,
            get_plan_prices_request,
            GetSubscriptionPlanPricesData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;

        let connector_integration = self.connector_enum.get_connector_integration();

        let response = self
            .call_connector(
                state,
                router_data,
                "get subscription plan prices",
                connector_integration,
            )
            .await?;
        self.handle_connector_response(response)
    }

    pub async fn pause_subscription_on_connector(
        &self,
        state: &SessionState,
        subscription: &hyperswitch_domain_models::subscription::Subscription,
        request: &subscription_types::PauseSubscriptionRequest,
    ) -> SubscriptionResult<subscription_response_types::SubscriptionPauseResponse> {
        let pause_subscription_request = subscription_request_types::SubscriptionPauseRequest {
            subscription_id: subscription.id.clone(),
            pause_option: request.pause_option.clone(),
            pause_date: request.pause_at,
        };

        let router_data = self.build_router_data(
            state,
            pause_subscription_request,
            SubscriptionPauseData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;
        let connector_integration = self.connector_enum.get_connector_integration();
        let response = self
            .call_connector(
                state,
                router_data,
                "pause subscription",
                connector_integration,
            )
            .await?;
        self.handle_connector_response(response)
    }

    pub async fn resume_subscription_on_connector(
        &self,
        state: &SessionState,
        subscription: &hyperswitch_domain_models::subscription::Subscription,
        request: &subscription_types::ResumeSubscriptionRequest,
    ) -> SubscriptionResult<subscription_response_types::SubscriptionResumeResponse> {
        let resume_subscription_request = subscription_request_types::SubscriptionResumeRequest {
            subscription_id: subscription.id.clone(),
            resume_date: request.resume_date,
            charges_handling: request.charges_handling.clone(),
            resume_option: request.resume_option.clone(),
            unpaid_invoices_handling: request.unpaid_invoices_handling.clone(),
        };

        let router_data = self.build_router_data(
            state,
            resume_subscription_request,
            SubscriptionResumeData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;
        let connector_integration = self.connector_enum.get_connector_integration();
        let response = self
            .call_connector(
                state,
                router_data,
                "resume subscription",
                connector_integration,
            )
            .await?;
        self.handle_connector_response(response)
    }

    pub async fn cancel_subscription_on_connector(
        &self,
        state: &SessionState,
        subscription: &hyperswitch_domain_models::subscription::Subscription,
        request: &subscription_types::CancelSubscriptionRequest,
    ) -> SubscriptionResult<subscription_response_types::SubscriptionCancelResponse> {
        let cancel_subscription_request = subscription_request_types::SubscriptionCancelRequest {
            subscription_id: subscription.id.clone(),
            cancel_date: request.cancel_at,
            account_receivables_handling: request.account_receivables_handling.clone(),
            cancel_option: request.cancel_option.clone(),
            cancel_reason_code: request.cancel_reason_code.clone(),
            credit_option_for_current_term_charges: request
                .credit_option_for_current_term_charges
                .clone(),
            refundable_credits_handling: request.refundable_credits_handling.clone(),
            unbilled_charges_option: request.unbilled_charges_option.clone(),
        };

        let router_data = self.build_router_data(
            state,
            cancel_subscription_request,
            SubscriptionCancelData {
                connector_meta_data: self.connector_metadata.clone(),
            },
        )?;
        let connector_integration = self.connector_enum.get_connector_integration();
        let response = self
            .call_connector(
                state,
                router_data,
                "cancel subscription",
                connector_integration,
            )
            .await?;
        self.handle_connector_response(response)
    }

    async fn call_connector<F, ResourceCommonData, Req, Resp>(
        &self,
        state: &SessionState,
        router_data: hyperswitch_domain_models::router_data_v2::RouterDataV2<
            F,
            ResourceCommonData,
            Req,
            Resp,
        >,
        operation_name: &str,
        connector_integration: connector_integration_interface::BoxedConnectorIntegrationInterface<
            F,
            ResourceCommonData,
            Req,
            Resp,
        >,
    ) -> SubscriptionResult<Result<Resp, hyperswitch_domain_models::router_data::ErrorResponse>>
    where
        F: Clone + std::fmt::Debug + 'static,
        Req: Clone + std::fmt::Debug + 'static,
        Resp: Clone + std::fmt::Debug + 'static,
        ResourceCommonData:
            connector_integration_interface::RouterDataConversion<F, Req, Resp> + Clone + 'static,
    {
        let old_router_data = ResourceCommonData::to_old_router_data(router_data).change_context(
            errors::ApiErrorResponse::SubscriptionError {
                operation: { operation_name.to_string() },
            },
        )?;

        let router_resp = api_client::execute_connector_processing_step(
            state,
            connector_integration,
            &old_router_data,
            CallConnectorAction::Trigger,
            None,
            None,
        )
        .await
        .change_context(errors::ApiErrorResponse::SubscriptionError {
            operation: operation_name.to_string(),
        })
        .attach_printable(format!(
            "Failed while in subscription operation: {operation_name}"
        ))?;

        Ok(router_resp.response)
    }

    fn build_router_data<F, ResourceCommonData, Req, Resp>(
        &self,
        state: &SessionState,
        req: Req,
        resource_common_data: ResourceCommonData,
    ) -> SubscriptionResult<
        hyperswitch_domain_models::router_data_v2::RouterDataV2<F, ResourceCommonData, Req, Resp>,
    > {
        Ok(hyperswitch_domain_models::router_data_v2::RouterDataV2 {
            flow: std::marker::PhantomData,
            connector_auth_type: self.auth_type.clone(),
            resource_common_data,
            tenant_id: state.tenant.tenant_id.clone(),
            request: req,
            response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()),
        })
    }

    fn handle_connector_response<T>(
        &self,
        response: Result<T, hyperswitch_domain_models::router_data::ErrorResponse>,
    ) -> SubscriptionResult<T> {
        match response {
            Ok(resp) => Ok(resp),
            Err(err) => Err(errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: self.connector_name.to_string(),
                status_code: err.status_code,
                reason: err.reason,
            }
            .into()),
        }
    }
}


// File: crates/subscriptions/src/core/errors.rs

pub use common_utils::errors::{CustomResult, ParsingError, ValidationError};
pub use hyperswitch_domain_models::{
    api,
    errors::api_error_response::{self, *},
};

pub type SubscriptionResult<T> = CustomResult<T, ApiErrorResponse>;