Bug: feature: Subscriptions get plans



## description
Get plans api for subscriptions
diff --git a/crates/api_models/src/subscription.rs b/crates/api_models/src/subscription.rs
index c9cc3acd2ce..9378d7796f4 100644
--- a/crates/api_models/src/subscription.rs
+++ b/crates/api_models/src/subscription.rs
@@ -141,8 +141,58 @@ impl SubscriptionResponse {
     }
 }
 
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct GetPlansResponse {
+    pub plan_id: String,
+    pub name: String,
+    pub description: Option<String>,
+    pub price_id: Vec<SubscriptionPlanPrices>,
+}
+
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SubscriptionPlanPrices {
+    pub price_id: String,
+    pub plan_id: Option<String>,
+    pub amount: MinorUnit,
+    pub currency: api_enums::Currency,
+    pub interval: PeriodUnit,
+    pub interval_count: i64,
+    pub trial_period: Option<i64>,
+    pub trial_period_unit: Option<PeriodUnit>,
+}
+
+#[derive(Debug, Clone, serde::Serialize)]
+pub enum PeriodUnit {
+    Day,
+    Week,
+    Month,
+    Year,
+}
+
+#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub struct ClientSecret(String);
+
+impl ClientSecret {
+    pub fn new(secret: String) -> Self {
+        Self(secret)
+    }
+
+    pub fn as_str(&self) -> &str {
+        &self.0
+    }
+}
+
+#[derive(serde::Deserialize, serde::Serialize, Debug)]
+pub struct GetPlansQuery {
+    pub client_secret: Option<ClientSecret>,
+    pub limit: Option<u32>,
+    pub offset: Option<u32>,
+}
+
 impl ApiEventMetric for SubscriptionResponse {}
 impl ApiEventMetric for CreateSubscriptionRequest {}
+impl ApiEventMetric for GetPlansQuery {}
+impl ApiEventMetric for GetPlansResponse {}
 
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
 pub struct ConfirmSubscriptionPaymentDetails {
@@ -227,6 +277,7 @@ pub struct PaymentResponseData {
     pub error_code: Option<String>,
     pub error_message: Option<String>,
     pub payment_method_type: Option<api_enums::PaymentMethodType>,
+    pub client_secret: Option<String>,
 }
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
 pub struct ConfirmSubscriptionRequest {
diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee.rs b/crates/hyperswitch_connectors/src/connectors/chargebee.rs
index b2d66db1f4b..f3e935c6dd5 100644
--- a/crates/hyperswitch_connectors/src/connectors/chargebee.rs
+++ b/crates/hyperswitch_connectors/src/connectors/chargebee.rs
@@ -843,7 +843,13 @@ fn get_chargebee_plans_query_params(
 ) -> CustomResult<String, errors::ConnectorError> {
     // Try to get limit from request, else default to 10
     let limit = _req.request.limit.unwrap_or(10);
-    let param = format!("?limit={}&type[is]={}", limit, constants::PLAN_ITEM_TYPE);
+    let offset = _req.request.offset.unwrap_or(0);
+    let param = format!(
+        "?limit={}&offset={}&type[is]={}",
+        limit,
+        offset,
+        constants::PLAN_ITEM_TYPE
+    );
     Ok(param)
 }
 
diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
index 4c7b135f1a2..324418e22b7 100644
--- a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
@@ -1248,12 +1248,13 @@ pub struct ChargebeePlanPriceItem {
     pub period: i64,
     pub period_unit: ChargebeePeriodUnit,
     pub trial_period: Option<i64>,
-    pub trial_period_unit: ChargebeeTrialPeriodUnit,
+    pub trial_period_unit: Option<ChargebeeTrialPeriodUnit>,
     pub price: MinorUnit,
     pub pricing_model: ChargebeePricingModel,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
 pub enum ChargebeePricingModel {
     FlatFee,
     PerUnit,
@@ -1263,6 +1264,7 @@ pub enum ChargebeePricingModel {
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
 pub enum ChargebeePeriodUnit {
     Day,
     Week,
@@ -1271,6 +1273,7 @@ pub enum ChargebeePeriodUnit {
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
 pub enum ChargebeeTrialPeriodUnit {
     Day,
     Month,
@@ -1308,8 +1311,9 @@ impl<F, T>
                 interval_count: prices.item_price.period,
                 trial_period: prices.item_price.trial_period,
                 trial_period_unit: match prices.item_price.trial_period_unit {
-                    ChargebeeTrialPeriodUnit::Day => Some(subscriptions::PeriodUnit::Day),
-                    ChargebeeTrialPeriodUnit::Month => Some(subscriptions::PeriodUnit::Month),
+                    Some(ChargebeeTrialPeriodUnit::Day) => Some(subscriptions::PeriodUnit::Day),
+                    Some(ChargebeeTrialPeriodUnit::Month) => Some(subscriptions::PeriodUnit::Month),
+                    None => None,
                 },
             })
             .collect();
diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs
index 70f7c4830cf..3ed5130e077 100644
--- a/crates/hyperswitch_domain_models/src/lib.rs
+++ b/crates/hyperswitch_domain_models/src/lib.rs
@@ -37,6 +37,7 @@ pub mod router_flow_types;
 pub mod router_request_types;
 pub mod router_response_types;
 pub mod routing;
+pub mod subscription;
 #[cfg(feature = "tokenization_v2")]
 pub mod tokenization;
 pub mod transformers;
diff --git a/crates/hyperswitch_domain_models/src/router_request_types/subscriptions.rs b/crates/hyperswitch_domain_models/src/router_request_types/subscriptions.rs
index 8599116e8f4..f93185b7d30 100644
--- a/crates/hyperswitch_domain_models/src/router_request_types/subscriptions.rs
+++ b/crates/hyperswitch_domain_models/src/router_request_types/subscriptions.rs
@@ -29,6 +29,21 @@ pub struct GetSubscriptionPlansRequest {
     pub offset: Option<u32>,
 }
 
+impl GetSubscriptionPlansRequest {
+    pub fn new(limit: Option<u32>, offset: Option<u32>) -> Self {
+        Self { limit, offset }
+    }
+}
+
+impl Default for GetSubscriptionPlansRequest {
+    fn default() -> Self {
+        Self {
+            limit: Some(10),
+            offset: Some(0),
+        }
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct GetSubscriptionPlanPricesRequest {
     pub plan_price_id: String,
diff --git a/crates/hyperswitch_domain_models/src/router_response_types/subscriptions.rs b/crates/hyperswitch_domain_models/src/router_response_types/subscriptions.rs
index 3da78e63beb..183b2f7ed38 100644
--- a/crates/hyperswitch_domain_models/src/router_response_types/subscriptions.rs
+++ b/crates/hyperswitch_domain_models/src/router_response_types/subscriptions.rs
@@ -33,6 +33,7 @@ pub enum SubscriptionStatus {
     Onetime,
     Cancelled,
     Failed,
+    Created,
 }
 
 #[cfg(feature = "v1")]
@@ -47,6 +48,7 @@ impl From<SubscriptionStatus> for api_models::subscription::SubscriptionStatus {
             SubscriptionStatus::Onetime => Self::Onetime,
             SubscriptionStatus::Cancelled => Self::Cancelled,
             SubscriptionStatus::Failed => Self::Failed,
+            SubscriptionStatus::Created => Self::Created,
         }
     }
 }
@@ -80,6 +82,22 @@ pub struct SubscriptionPlanPrices {
     pub trial_period_unit: Option<PeriodUnit>,
 }
 
+#[cfg(feature = "v1")]
+impl From<SubscriptionPlanPrices> for api_models::subscription::SubscriptionPlanPrices {
+    fn from(item: SubscriptionPlanPrices) -> Self {
+        Self {
+            price_id: item.price_id,
+            plan_id: item.plan_id,
+            amount: item.amount,
+            currency: item.currency,
+            interval: item.interval.into(),
+            interval_count: item.interval_count,
+            trial_period: item.trial_period,
+            trial_period_unit: item.trial_period_unit.map(Into::into),
+        }
+    }
+}
+
 #[derive(Debug, Clone)]
 pub enum PeriodUnit {
     Day,
@@ -88,6 +106,18 @@ pub enum PeriodUnit {
     Year,
 }
 
+#[cfg(feature = "v1")]
+impl From<PeriodUnit> for api_models::subscription::PeriodUnit {
+    fn from(unit: PeriodUnit) -> Self {
+        match unit {
+            PeriodUnit::Day => Self::Day,
+            PeriodUnit::Week => Self::Week,
+            PeriodUnit::Month => Self::Month,
+            PeriodUnit::Year => Self::Year,
+        }
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct GetSubscriptionEstimateResponse {
     pub sub_total: MinorUnit,
diff --git a/crates/hyperswitch_domain_models/src/subscription.rs b/crates/hyperswitch_domain_models/src/subscription.rs
new file mode 100644
index 00000000000..af2390de3f8
--- /dev/null
+++ b/crates/hyperswitch_domain_models/src/subscription.rs
@@ -0,0 +1,50 @@
+use common_utils::events::ApiEventMetric;
+use error_stack::ResultExt;
+
+use crate::errors::api_error_response::ApiErrorResponse;
+
+const SECRET_SPLIT: &str = "_secret";
+
+#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub struct ClientSecret(String);
+
+impl ClientSecret {
+    pub fn new(secret: String) -> Self {
+        Self(secret)
+    }
+
+    pub fn get_subscription_id(&self) -> error_stack::Result<String, ApiErrorResponse> {
+        let sub_id = self
+            .0
+            .split(SECRET_SPLIT)
+            .next()
+            .ok_or(ApiErrorResponse::MissingRequiredField {
+                field_name: "client_secret",
+            })
+            .attach_printable("Failed to extract subscription_id from client_secret")?;
+
+        Ok(sub_id.to_string())
+    }
+}
+
+impl std::fmt::Display for ClientSecret {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl ApiEventMetric for ClientSecret {}
+
+#[cfg(feature = "v1")]
+impl From<api_models::subscription::ClientSecret> for ClientSecret {
+    fn from(api_secret: api_models::subscription::ClientSecret) -> Self {
+        Self::new(api_secret.as_str().to_string())
+    }
+}
+
+#[cfg(feature = "v1")]
+impl From<ClientSecret> for api_models::subscription::ClientSecret {
+    fn from(domain_secret: ClientSecret) -> Self {
+        Self::new(domain_secret.to_string())
+    }
+}
diff --git a/crates/router/src/core/subscription.rs b/crates/router/src/core/subscription.rs
index 7ef35b8c0d1..1ec8514fc65 100644
--- a/crates/router/src/core/subscription.rs
+++ b/crates/router/src/core/subscription.rs
@@ -4,7 +4,10 @@ use api_models::subscription::{
 use common_enums::connector_enums;
 use common_utils::id_type::GenerateId;
 use error_stack::ResultExt;
-use hyperswitch_domain_models::{api::ApplicationResponse, merchant_context::MerchantContext};
+use hyperswitch_domain_models::{
+    api::ApplicationResponse, merchant_context::MerchantContext,
+    router_response_types::subscriptions as subscription_response_types,
+};
 
 use super::errors::{self, RouterResponse};
 use crate::{
@@ -28,7 +31,7 @@ pub async fn create_subscription(
     merchant_context: MerchantContext,
     profile_id: common_utils::id_type::ProfileId,
     request: subscription_types::CreateSubscriptionRequest,
-) -> RouterResponse<SubscriptionResponse> {
+) -> RouterResponse<subscription_types::ConfirmSubscriptionResponse> {
     let subscription_id = common_utils::id_type::SubscriptionId::generate();
 
     let profile =
@@ -43,7 +46,7 @@ pub async fn create_subscription(
         &state,
         merchant_context.get_merchant_account(),
         merchant_context.get_merchant_key_store(),
-        customer,
+        Some(customer),
         profile.clone(),
     )
     .await?;
@@ -65,7 +68,7 @@ pub async fn create_subscription(
         .create_payment_with_confirm_false(subscription.handler.state, &request)
         .await
         .attach_printable("subscriptions: failed to create payment")?;
-    invoice_handler
+    let invoice_entry = invoice_handler
         .create_invoice_entry(
             &state,
             billing_handler.merchant_connector_id,
@@ -88,9 +91,67 @@ pub async fn create_subscription(
         .await
         .attach_printable("subscriptions: failed to update subscription")?;
 
-    Ok(ApplicationResponse::Json(
-        subscription.to_subscription_response(),
-    ))
+    let response = subscription.generate_response(
+        &invoice_entry,
+        &payment,
+        subscription_response_types::SubscriptionStatus::Created,
+    )?;
+
+    Ok(ApplicationResponse::Json(response))
+}
+
+pub async fn get_subscription_plans(
+    state: SessionState,
+    merchant_context: MerchantContext,
+    profile_id: common_utils::id_type::ProfileId,
+    query: subscription_types::GetPlansQuery,
+) -> RouterResponse<Vec<subscription_types::GetPlansResponse>> {
+    let profile =
+        SubscriptionHandler::find_business_profile(&state, &merchant_context, &profile_id)
+            .await
+            .attach_printable("subscriptions: failed to find business profile")?;
+
+    let subscription_handler = SubscriptionHandler::new(&state, &merchant_context);
+
+    if let Some(client_secret) = query.client_secret {
+        subscription_handler
+            .find_and_validate_subscription(&client_secret.into())
+            .await?
+    };
+
+    let billing_handler = BillingHandler::create(
+        &state,
+        merchant_context.get_merchant_account(),
+        merchant_context.get_merchant_key_store(),
+        None,
+        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.
@@ -116,7 +177,7 @@ pub async fn create_and_confirm_subscription(
         &state,
         merchant_context.get_merchant_account(),
         merchant_context.get_merchant_key_store(),
-        customer,
+        Some(customer),
         profile.clone(),
     )
     .await?;
@@ -259,7 +320,7 @@ pub async fn confirm_subscription(
         &state,
         merchant_context.get_merchant_account(),
         merchant_context.get_merchant_key_store(),
-        customer,
+        Some(customer),
         profile.clone(),
     )
     .await?;
diff --git a/crates/router/src/core/subscription/billing_processor_handler.rs b/crates/router/src/core/subscription/billing_processor_handler.rs
index 4cc8ad31073..2e1d3b7ff7e 100644
--- a/crates/router/src/core/subscription/billing_processor_handler.rs
+++ b/crates/router/src/core/subscription/billing_processor_handler.rs
@@ -5,7 +5,8 @@ use common_utils::{ext_traits::ValueExt, pii};
 use error_stack::ResultExt;
 use hyperswitch_domain_models::{
     router_data_v2::flow_common_types::{
-        InvoiceRecordBackData, SubscriptionCreateData, SubscriptionCustomerData,
+        GetSubscriptionPlanPricesData, GetSubscriptionPlansData, InvoiceRecordBackData,
+        SubscriptionCreateData, SubscriptionCustomerData,
     },
     router_request_types::{
         revenue_recovery::InvoiceRecordBackRequest, subscriptions as subscription_request_types,
@@ -27,7 +28,7 @@ pub struct BillingHandler {
     pub connector_data: api_types::ConnectorData,
     pub connector_params: hyperswitch_domain_models::connector_endpoints::ConnectorParams,
     pub connector_metadata: Option<pii::SecretSerdeValue>,
-    pub customer: hyperswitch_domain_models::customer::Customer,
+    pub customer: Option<hyperswitch_domain_models::customer::Customer>,
     pub merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
 }
 
@@ -37,7 +38,7 @@ impl BillingHandler {
         state: &SessionState,
         merchant_account: &hyperswitch_domain_models::merchant_account::MerchantAccount,
         key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
-        customer: hyperswitch_domain_models::customer::Customer,
+        customer: Option<hyperswitch_domain_models::customer::Customer>,
         profile: hyperswitch_domain_models::business_profile::Profile,
     ) -> errors::RouterResult<Self> {
         let merchant_connector_id = profile.get_billing_processor_id()?;
@@ -109,8 +110,15 @@ impl BillingHandler {
         billing_address: Option<api_models::payments::Address>,
         payment_method_data: Option<api_models::payments::PaymentMethodData>,
     ) -> errors::RouterResult<ConnectorCustomerResponseData> {
+        let customer =
+            self.customer
+                .as_ref()
+                .ok_or(errors::ApiErrorResponse::MissingRequiredField {
+                    field_name: "customer",
+                })?;
+
         let customer_req = ConnectorCustomerData {
-            email: self.customer.email.clone().map(pii::Email::from),
+            email: customer.email.clone().map(pii::Email::from),
             payment_method_data: payment_method_data.clone().map(|pmd| pmd.into()),
             description: None,
             phone: None,
@@ -275,6 +283,87 @@ impl BillingHandler {
         }
     }
 
+    pub async fn get_subscription_plans(
+        &self,
+        state: &SessionState,
+        limit: Option<u32>,
+        offset: Option<u32>,
+    ) -> errors::RouterResult<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_data.connector.get_connector_integration();
+
+        let response = self
+            .call_connector(
+                state,
+                router_data,
+                "get subscription plans",
+                connector_integration,
+            )
+            .await?;
+
+        match response {
+            Ok(resp) => Ok(resp),
+            Err(err) => Err(errors::ApiErrorResponse::ExternalConnectorError {
+                code: err.code,
+                message: err.message,
+                connector: self.connector_data.connector_name.to_string().clone(),
+                status_code: err.status_code,
+                reason: err.reason,
+            }
+            .into()),
+        }
+    }
+
+    pub async fn get_subscription_plan_prices(
+        &self,
+        state: &SessionState,
+        plan_price_id: String,
+    ) -> errors::RouterResult<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_data.connector.get_connector_integration();
+
+        let response = self
+            .call_connector(
+                state,
+                router_data,
+                "get subscription plan prices",
+                connector_integration,
+            )
+            .await?;
+
+        match response {
+            Ok(resp) => Ok(resp),
+            Err(err) => Err(errors::ApiErrorResponse::ExternalConnectorError {
+                code: err.code,
+                message: err.message,
+                connector: self.connector_data.connector_name.to_string().clone(),
+                status_code: err.status_code,
+                reason: err.reason,
+            }
+            .into()),
+        }
+    }
+
     async fn call_connector<F, ResourceCommonData, Req, Resp>(
         &self,
         state: &SessionState,
diff --git a/crates/router/src/core/subscription/subscription_handler.rs b/crates/router/src/core/subscription/subscription_handler.rs
index 24c08313b82..b11cf517b03 100644
--- a/crates/router/src/core/subscription/subscription_handler.rs
+++ b/crates/router/src/core/subscription/subscription_handler.rs
@@ -5,6 +5,7 @@ use api_models::{
     subscription::{self as subscription_types, SubscriptionResponse, SubscriptionStatus},
 };
 use common_enums::connector_enums;
+use common_utils::{consts, ext_traits::OptionExt};
 use diesel_models::subscription::SubscriptionNew;
 use error_stack::ResultExt;
 use hyperswitch_domain_models::{
@@ -122,6 +123,60 @@ impl<'a> SubscriptionHandler<'a> {
             })
     }
 
+    pub async fn find_and_validate_subscription(
+        &self,
+        client_secret: &hyperswitch_domain_models::subscription::ClientSecret,
+    ) -> errors::RouterResult<()> {
+        let subscription_id = client_secret.get_subscription_id()?;
+
+        let subscription = self
+            .state
+            .store
+            .find_by_merchant_id_subscription_id(
+                self.merchant_context.get_merchant_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: &diesel_models::subscription::Subscription,
+    ) -> errors::RouterResult<()> {
+        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,
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 60724830e0c..57a9aeb1f7f 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1187,6 +1187,9 @@ impl Subscription {
                     subscription::create_subscription(state, req, payload)
                 }),
             ))
+            .service(
+                web::resource("/plans").route(web::get().to(subscription::get_subscription_plans)),
+            )
             .service(
                 web::resource("/{subscription_id}/confirm").route(web::post().to(
                     |state, req, id, payload| {
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index f6ba2af979a..713b51a9d77 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -87,12 +87,11 @@ impl From<Flow> for ApiIdentifier {
             | Flow::VolumeSplitOnRoutingType
             | Flow::DecisionEngineDecideGatewayCall
             | Flow::DecisionEngineGatewayFeedbackCall => Self::Routing,
-
             Flow::CreateSubscription
             | Flow::ConfirmSubscription
             | Flow::CreateAndConfirmSubscription
-            | Flow::GetSubscription => Self::Subscription,
-
+            | Flow::GetSubscription
+            | Flow::GetPlansForSubscription => Self::Subscription,
             Flow::RetrieveForexFlow => Self::Forex,
             Flow::AddToBlocklist => Self::Blocklist,
             Flow::DeleteFromBlocklist => Self::Blocklist,
diff --git a/crates/router/src/routes/subscription.rs b/crates/router/src/routes/subscription.rs
index a762da71972..46432810450 100644
--- a/crates/router/src/routes/subscription.rs
+++ b/crates/router/src/routes/subscription.rs
@@ -60,6 +60,7 @@ pub async fn create_subscription(
         Ok(id) => id,
         Err(response) => return response,
     };
+
     Box::pin(oss_api::server_wrap(
         flow,
         state,
@@ -104,6 +105,7 @@ pub async fn confirm_subscription(
         Ok(id) => id,
         Err(response) => return response,
     };
+
     Box::pin(oss_api::server_wrap(
         flow,
         state,
@@ -136,6 +138,41 @@ pub async fn confirm_subscription(
     .await
 }
 
+#[instrument(skip_all)]
+pub async fn get_subscription_plans(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    query: web::Query<subscription_types::GetPlansQuery>,
+) -> impl Responder {
+    let flow = Flow::GetPlansForSubscription;
+    let api_auth = auth::ApiKeyAuth::default();
+
+    let profile_id = match extract_profile_id(&req) {
+        Ok(profile_id) => profile_id,
+        Err(response) => return response,
+    };
+
+    let auth_data = match auth::is_ephemeral_auth(req.headers(), api_auth) {
+        Ok(auth) => auth,
+        Err(err) => return crate::services::api::log_and_return_error_response(err),
+    };
+    Box::pin(oss_api::server_wrap(
+        flow,
+        state,
+        &req,
+        query.into_inner(),
+        |state, auth: auth::AuthenticationData, query, _| {
+            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
+                domain::Context(auth.merchant_account, auth.key_store),
+            ));
+            subscription::get_subscription_plans(state, merchant_context, profile_id.clone(), query)
+        },
+        &*auth_data,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 /// Add support for get subscription by id
 #[instrument(skip_all)]
 pub async fn get_subscription(
diff --git a/crates/router/src/workflows/invoice_sync.rs b/crates/router/src/workflows/invoice_sync.rs
index 83aa45dc840..ec2d2d7a913 100644
--- a/crates/router/src/workflows/invoice_sync.rs
+++ b/crates/router/src/workflows/invoice_sync.rs
@@ -171,7 +171,7 @@ impl<'a> InvoiceSyncHandler<'a> {
             self.state,
             &self.merchant_account,
             &self.key_store,
-            self.customer.clone(),
+            Some(self.customer.clone()),
             self.profile.clone(),
         )
         .await
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 46eef6fab6b..95582124cc5 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -265,6 +265,8 @@ pub enum Flow {
     RoutingDeleteConfig,
     /// Subscription create flow,
     CreateSubscription,
+    /// Subscription get plans flow,
+    GetPlansForSubscription,
     /// Subscription confirm flow,
     ConfirmSubscription,
     /// Subscription create and confirm flow,

This pull request introduces a new API endpoint for retrieving subscription plans and the supporting plumbing required for it. The changes include new request/response types, authentication and expiry logic, and routing updates to expose the endpoint. The main themes are new subscription plan retrieval functionality and related codebase integration.

**Subscription Plan Retrieval Functionality**
* Added a new API endpoint `/subscription/plans/{client_secret}` to fetch available subscription plans, including routing and handler logic in `app.rs` and `subscription.rs`. [[1]](diffhunk://#diff-a6e49b399ffdee83dd322b3cb8bee170c65fb9c6e2e52c14222ec310f6927c54L1176-R1186) [[2]](diffhunk://#diff-d737ada25c830476b3867c693bd362d4b1142773f1b0d971556518b5c04691a3R70-R105)
* Implemented the `get_subscription_plans` function in `core/subscription.rs`, which authenticates the client secret, fetches the relevant subscription, and integrates with connector services to retrieve plans.
* Introduced helper functions in `core/utils/subscription.rs` for authenticating client secrets and constructing router data for connector calls.

**Type and Model Updates**
* Added new request and response types for subscription plan retrieval, including `GetPlansResponse` and `GetSubscriptionPlansRequest` with appropriate traits and serialization. [[1]](diffhunk://#diff-3d85d9e288cc67856867986ee406a3102ce1d0cf9fe5c8f9efed3a578608f817R108-R117) [[2]](diffhunk://#diff-8eb3ff11b17aa944306848a821a22486cbad862e5100916531fdcee5a6718636L26-R26)

**Routing and Flow Integration**
* Updated flow enums and routing logic to support the new subscription plans flow, ensuring correct API identifier mapping and logging. [[1]](diffhunk://#diff-f57d3f577ce7478b78b53d715f35fe97acd4f02ef2043738f053a15100a13ba5R268-R269) [[2]](diffhunk://#diff-0034b07e0697eb780f74742326da73d1e85183054d879fe55143a0ab39c0ee99L93-R93)

**Module Organization**
* Registered the new `subscription` module under `core/utils.rs` for better code organization.## Type of Change


### Additional Changes

- [x] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->

1. Create charge-bee mca
2.  Create subscription
```
curl --location 'http://localhost:8080/subscription/create' \
--header 'Content-Type: application/json' \
--header 'X-Profile-Id: pro_dQr2rAikRT2xWZr7R1QL' \
--header 'api-key: dev_wSnWYnMrVFSJ0N25i99QT08Jmx1WBurtbuDl3Ks0XQ1RQn3URdM4UntOenSPHHmT' \
--data '{
    "customer_id": "cus_jtJ8WHTHnsguskpiorEt"
}'
```
3. update db (subscription table) with the mca_id
```
UPDATE subscription
SET merchant_connector_id = 'mca_QRvgtteGBG6Ip2g1gKtD'
WHERE id = 'subscription_vFcn0H8kSTU9bE3Khe05';
```

4. Get plans(with client_secret)
```
curl --location 'http://localhost:8080/subscription/plans' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_Ske75Nx2J7qtHsP8cc7pFx5k4dccYBedM6UAExaLOdHCkji3uVWSqfmZ0Qz0Tnyj' \
--header 'X-Profile-Id: pro_2WzEeiNyj8fSCObXqo36'
```
Response:
```
[
  {
    "plan_id": "cbdemo_enterprise-suite",
    "name": "Enterprise Suite",
    "description": "High-end customer support suite with enterprise-grade solutions.",
    "price_id": [
      {
        "price_id": "cbdemo_enterprise-suite-monthly",
        "plan_id": "cbdemo_enterprise-suite",
        "amount": 14100,
        "currency": "INR",
        "interval": "Month",
        "interval_count": 1,
        "trial_period": null,
        "trial_period_unit": null
      },
      {
        "price_id": "cbdemo_enterprise-suite-annual",
        "plan_id": "cbdemo_enterprise-suite",
        "amount": 169000,
        "currency": "INR",
        "interval": "Year",
        "interval_count": 1,
        "trial_period": null,
        "trial_period_unit": null
      }
    ]
  },
  {
    "plan_id": "cbdemo_business-suite",
    "name": "Business Suite",
    "description": "Advanced customer support plan with premium features.",
    "price_id": [
      {
        "price_id": "cbdemo_business-suite-monthly",
        "plan_id": "cbdemo_business-suite",
        "amount": 9600,
        "currency": "INR",
        "interval": "Month",
        "interval_count": 1,
        "trial_period": null,
        "trial_period_unit": null
      },
      {
        "price_id": "cbdemo_business-suite-annual",
        "plan_id": "cbdemo_business-suite",
        "amount": 115000,
        "currency": "INR",
        "interval": "Year",
        "interval_count": 1,
        "trial_period": null,
        "trial_period_unit": null
      }
    ]
  },
  {
    "plan_id": "cbdemo_professional-suite",
    "name": "Professional Suite",
    "description": "Comprehensive customer support plan with extended capabilities.",
    "price_id": [
      {
        "price_id": "cbdemo_professional-suite-monthly",
        "plan_id": "cbdemo_professional-suite",
        "amount": 4600,
        "currency": "INR",
        "interval": "Month",
        "interval_count": 1,
        "trial_period": null,
        "trial_period_unit": null
      },
      {
        "price_id": "cbdemo_professional-suite-annual",
        "plan_id": "cbdemo_professional-suite",
        "amount": 55000,
        "currency": "INR",
        "interval": "Year",
        "interval_count": 1,
        "trial_period": null,
        "trial_period_unit": null
      }
    ]
  },
  {
    "plan_id": "cbdemo_essential-support",
    "name": "Essential Support",
    "description": "Basic customer support plan with core features.",
    "price_id": [
      {
        "price_id": "cbdemo_essential-support-monthly",
        "plan_id": "cbdemo_essential-support",
        "amount": 1600,
        "currency": "INR",
        "interval": "Month",
        "interval_count": 1,
        "trial_period": null,
        "trial_period_unit": null
      },
      {
        "price_id": "cbdemo_essential-support-annual",
        "plan_id": "cbdemo_essential-support",
        "amount": 19000,
        "currency": "INR",
        "interval": "Year",
        "interval_count": 1,
        "trial_period": null,
        "trial_period_unit": null
      }
    ]
  }
]

```

5. Get plans(without client_secret)
```
curl --location 'http://localhost:8080/subscription/plans?subscription_id=subscription_CLjJKIwjKlhuHk1BNOWc' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_UknLiMdqzEcVS5XJiJLqY3kfRT7WznSlbGYkV9kVHGy9GLHmNlNWJxGCEGrljHwt'
```
Response:
```
[
    {
        "plan_id": "cbdemo_enterprise-suite",
        "name": "Enterprise Suite",
        "description": "High-end customer support suite with enterprise-grade solutions."
    },
    {
        "plan_id": "cbdemo_business-suite",
        "name": "Business Suite",
        "description": "Advanced customer support plan with premium features."
    },
    {
        "plan_id": "cbdemo_professional-suite",
        "name": "Professional Suite",
        "description": "Comprehensive customer support plan with extended capabilities."
    },
    {
        "plan_id": "cbdemo_essential-support",
        "name": "Essential Support",
        "description": "Basic customer support plan with core features."
    }
]
```

Subscriptions create - 
```
curl --location 'http://localhost:8080/subscriptions/create' \
--header 'Content-Type: application/json' \
--header 'X-Profile-Id: pro_2WzEeiNyj8fSCObXqo36' \
--header 'api-key: dev_Ske75Nx2J7qtHsP8cc7pFx5k4dccYBedM6UAExaLOdHCkji3uVWSqfmZ0Qz0Tnyj' \
--data '{
    "customer_id": "cus_zppXAwRyRF6yXH5PEqJd",
    "amount": 14100,
    "currency": "USD",
    "payment_details": {
        "authentication_type": "no_three_ds",
        "setup_future_usage": "off_session",
        "capture_method": "automatic",
        "return_url": "https://google.com"
    }
}'
```

Response - 
```
{"id":"sub_uIILXUeoPUSzKzl67wyE","merchant_reference_id":null,"status":"created","plan_id":null,"price_id":null,"coupon":null,"profile_id":"pro_2WzEeiNyj8fSCObXqo36","payment":{"payment_id":"pay_niPeSIobMcl8Du4mkYce","status":"requires_payment_method","amount":14100,"currency":"USD","connector":null,"payment_method_id":null,"payment_experience":null,"error_code":null,"error_message":null,"payment_method_type":null,"client_secret":"pay_niPeSIobMcl8Du4mkYce_secret_mdzBqY9TeWppcvjJLIEu"},"customer_id":"cus_zppXAwRyRF6yXH5PEqJd","invoice":{"id":"invoice_FJEWqTVRZUgfzfAoowLi","subscription_id":"sub_uIILXUeoPUSzKzl67wyE","merchant_id":"merchant_1758626894","profile_id":"pro_2WzEeiNyj8fSCObXqo36","merchant_connector_id":"mca_eN6JxSK2NkuT0wSYAH5s","payment_intent_id":"pay_niPeSIobMcl8Du4mkYce","payment_method_id":null,"customer_id":"cus_zppXAwRyRF6yXH5PEqJd","amount":14100,"currency":"USD","status":"invoice_created"},"billing_processor_subscription_id":null}

```
## Checklist
<!-- Put an `x` in the boxes that apply -->

- [ ] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
