Bug: [FEATURE] Add Client Auth for Subscriptions APIs



### Feature Description

Need to implement auth for client side interactions


### Possible Implementation

Subscriptions APIs need to support publishible_key + client_secret auth

### Have you spent some time checking if this feature request has been raised before?

- [x] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [x] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/crates/api_models/src/subscription.rs b/crates/api_models/src/subscription.rs
index 2829d58b883..7c89eec5e52 100644
--- a/crates/api_models/src/subscription.rs
+++ b/crates/api_models/src/subscription.rs
@@ -74,6 +74,12 @@ pub struct SubscriptionResponse {
 
     /// Optional customer ID associated with this subscription.
     pub customer_id: common_utils::id_type::CustomerId,
+
+    /// Payment details for the invoice.
+    pub payment: Option<PaymentResponseData>,
+
+    /// Invoice Details for the subscription.
+    pub invoice: Option<Invoice>,
 }
 
 /// Possible states of a subscription lifecycle.
@@ -127,6 +133,8 @@ impl SubscriptionResponse {
         merchant_id: common_utils::id_type::MerchantId,
         client_secret: Option<Secret<String>>,
         customer_id: common_utils::id_type::CustomerId,
+        payment: Option<PaymentResponseData>,
+        invoice: Option<Invoice>,
     ) -> Self {
         Self {
             id,
@@ -138,6 +146,8 @@ impl SubscriptionResponse {
             merchant_id,
             coupon_code: None,
             customer_id,
+            payment,
+            invoice,
         }
     }
 }
@@ -181,6 +191,10 @@ impl ClientSecret {
     pub fn as_str(&self) -> &str {
         &self.0
     }
+
+    pub fn as_string(&self) -> &String {
+        &self.0
+    }
 }
 
 #[derive(serde::Deserialize, serde::Serialize, Debug)]
@@ -197,6 +211,7 @@ impl ApiEventMetric for GetPlansResponse {}
 
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
 pub struct ConfirmSubscriptionPaymentDetails {
+    pub shipping: Option<Address>,
     pub payment_method: api_enums::PaymentMethod,
     pub payment_method_type: Option<api_enums::PaymentMethodType>,
     pub payment_method_data: PaymentMethodDataRequest,
@@ -278,7 +293,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>,
+    pub client_secret: Option<Secret<String>>,
 }
 
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
@@ -294,7 +309,7 @@ pub struct CreateMitPaymentRequestData {
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
 pub struct ConfirmSubscriptionRequest {
     /// Client secret for SDK based interaction.
-    pub client_secret: Option<String>,
+    pub client_secret: Option<ClientSecret>,
 
     /// Identifier for the associated plan_id.
     pub plan_id: Option<String>,
@@ -305,15 +320,6 @@ pub struct ConfirmSubscriptionRequest {
     /// Idenctifier for the coupon code for the subscription.
     pub coupon_code: Option<String>,
 
-    /// Identifier for customer.
-    pub customer_id: common_utils::id_type::CustomerId,
-
-    /// Billing address for the subscription.
-    pub billing: Option<Address>,
-
-    /// Shipping address for the subscription.
-    pub shipping: Option<Address>,
-
     /// Payment details for the invoice.
     pub payment_details: ConfirmSubscriptionPaymentDetails,
 }
@@ -328,11 +334,15 @@ impl ConfirmSubscriptionRequest {
     }
 
     pub fn get_billing_address(&self) -> Result<Address, error_stack::Report<ValidationError>> {
-        self.billing.clone().ok_or(error_stack::report!(
-            ValidationError::MissingRequiredField {
-                field_name: "billing".to_string()
-            }
-        ))
+        self.payment_details
+            .payment_method_data
+            .billing
+            .clone()
+            .ok_or(error_stack::report!(
+                ValidationError::MissingRequiredField {
+                    field_name: "billing".to_string()
+                }
+            ))
     }
 }
 
diff --git a/crates/diesel_models/src/invoice.rs b/crates/diesel_models/src/invoice.rs
index 59ef0b272d5..2b93b0b4a62 100644
--- a/crates/diesel_models/src/invoice.rs
+++ b/crates/diesel_models/src/invoice.rs
@@ -23,6 +23,7 @@ pub struct InvoiceNew {
     pub metadata: Option<SecretSerdeValue>,
     pub created_at: time::PrimitiveDateTime,
     pub modified_at: time::PrimitiveDateTime,
+    pub connector_invoice_id: Option<String>,
 }
 
 #[derive(
@@ -49,6 +50,7 @@ pub struct Invoice {
     pub metadata: Option<SecretSerdeValue>,
     pub created_at: time::PrimitiveDateTime,
     pub modified_at: time::PrimitiveDateTime,
+    pub connector_invoice_id: Option<String>,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, AsChangeset, Deserialize)]
@@ -56,6 +58,7 @@ pub struct Invoice {
 pub struct InvoiceUpdate {
     pub status: Option<String>,
     pub payment_method_id: Option<String>,
+    pub connector_invoice_id: Option<String>,
     pub modified_at: time::PrimitiveDateTime,
     pub payment_intent_id: Option<common_utils::id_type::PaymentId>,
 }
@@ -75,6 +78,7 @@ impl InvoiceNew {
         status: InvoiceStatus,
         provider_name: Connector,
         metadata: Option<SecretSerdeValue>,
+        connector_invoice_id: Option<String>,
     ) -> Self {
         let id = common_utils::id_type::InvoiceId::generate();
         let now = common_utils::date_time::now();
@@ -94,6 +98,7 @@ impl InvoiceNew {
             metadata,
             created_at: now,
             modified_at: now,
+            connector_invoice_id,
         }
     }
 }
@@ -102,11 +107,13 @@ impl InvoiceUpdate {
     pub fn new(
         payment_method_id: Option<String>,
         status: Option<InvoiceStatus>,
+        connector_invoice_id: Option<String>,
         payment_intent_id: Option<common_utils::id_type::PaymentId>,
     ) -> Self {
         Self {
             payment_method_id,
             status: status.map(|status| status.to_string()),
+            connector_invoice_id,
             payment_intent_id,
             modified_at: common_utils::date_time::now(),
         }
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index bd91c7843e9..e3cd4336b4f 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -750,6 +750,8 @@ diesel::table! {
         metadata -> Nullable<Jsonb>,
         created_at -> Timestamp,
         modified_at -> Timestamp,
+        #[max_length = 64]
+        connector_invoice_id -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs
index 560d1fc6468..07f9683e83d 100644
--- a/crates/diesel_models/src/schema_v2.rs
+++ b/crates/diesel_models/src/schema_v2.rs
@@ -762,6 +762,8 @@ diesel::table! {
         metadata -> Nullable<Jsonb>,
         created_at -> Timestamp,
         modified_at -> Timestamp,
+        #[max_length = 64]
+        connector_invoice_id -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/router/src/core/subscription.rs b/crates/router/src/core/subscription.rs
index 2d3fd649669..5702f9b0a45 100644
--- a/crates/router/src/core/subscription.rs
+++ b/crates/router/src/core/subscription.rs
@@ -4,10 +4,7 @@ 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,
-    router_response_types::subscriptions as subscription_response_types,
-};
+use hyperswitch_domain_models::{api::ApplicationResponse, merchant_context::MerchantContext};
 
 use super::errors::{self, RouterResponse};
 use crate::{
@@ -31,7 +28,7 @@ pub async fn create_subscription(
     merchant_context: MerchantContext,
     profile_id: common_utils::id_type::ProfileId,
     request: subscription_types::CreateSubscriptionRequest,
-) -> RouterResponse<subscription_types::ConfirmSubscriptionResponse> {
+) -> RouterResponse<SubscriptionResponse> {
     let subscription_id = common_utils::id_type::SubscriptionId::generate();
 
     let profile =
@@ -68,7 +65,7 @@ pub async fn create_subscription(
         .create_payment_with_confirm_false(subscription.handler.state, &request)
         .await
         .attach_printable("subscriptions: failed to create payment")?;
-    let invoice_entry = invoice_handler
+    let invoice = invoice_handler
         .create_invoice_entry(
             &state,
             billing_handler.merchant_connector_id,
@@ -78,6 +75,7 @@ pub async fn create_subscription(
             connector_enums::InvoiceStatus::InvoiceCreated,
             billing_handler.connector_data.connector_name,
             None,
+            None,
         )
         .await
         .attach_printable("subscriptions: failed to create invoice")?;
@@ -91,11 +89,7 @@ pub async fn create_subscription(
         .await
         .attach_printable("subscriptions: failed to update subscription")?;
 
-    let response = subscription.generate_response(
-        &invoice_entry,
-        &payment,
-        subscription_response_types::SubscriptionStatus::Created,
-    )?;
+    let response = subscription.to_subscription_response(Some(payment), Some(&invoice))?;
 
     Ok(ApplicationResponse::Json(response))
 }
@@ -148,14 +142,12 @@ pub async fn get_subscription_plans(
                 .into_iter()
                 .map(subscription_types::SubscriptionPlanPrices::from)
                 .collect::<Vec<_>>(),
-        })
+        });
     }
-
     Ok(ApplicationResponse::Json(response))
 }
 
 /// Creates and confirms a subscription in one operation.
-/// This method combines the creation and confirmation flow to reduce API calls
 pub async fn create_and_confirm_subscription(
     state: SessionState,
     merchant_context: MerchantContext,
@@ -163,7 +155,6 @@ pub async fn create_and_confirm_subscription(
     request: subscription_types::CreateAndConfirmSubscriptionRequest,
 ) -> RouterResponse<subscription_types::ConfirmSubscriptionResponse> {
     let subscription_id = common_utils::id_type::SubscriptionId::generate();
-
     let profile =
         SubscriptionHandler::find_business_profile(&state, &merchant_context, &profile_id)
             .await
@@ -240,6 +231,9 @@ pub async fn create_and_confirm_subscription(
                 .unwrap_or(connector_enums::InvoiceStatus::InvoiceCreated),
             billing_handler.connector_data.connector_name,
             None,
+            invoice_details
+                .clone()
+                .map(|invoice| invoice.id.get_string_repr().to_string()),
         )
         .await?;
 
@@ -292,13 +286,23 @@ pub async fn confirm_subscription(
         SubscriptionHandler::find_business_profile(&state, &merchant_context, &profile_id)
             .await
             .attach_printable("subscriptions: failed to find business profile")?;
-    let customer =
-        SubscriptionHandler::find_customer(&state, &merchant_context, &request.customer_id)
-            .await
-            .attach_printable("subscriptions: failed to find customer")?;
 
     let handler = SubscriptionHandler::new(&state, &merchant_context);
+    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 customer = SubscriptionHandler::find_customer(
+        &state,
+        &merchant_context,
+        &subscription_entry.subscription.customer_id,
+    )
+    .await
+    .attach_printable("subscriptions: failed to find customer")?;
+
     let invoice_handler = subscription_entry.get_invoice_handler(profile.clone());
     let invoice = invoice_handler
         .get_latest_invoice(&state)
@@ -331,7 +335,7 @@ pub async fn confirm_subscription(
         .create_customer_on_connector(
             &state,
             subscription.customer_id.clone(),
-            request.billing.clone(),
+            request.payment_details.payment_method_data.billing.clone(),
             request
                 .payment_details
                 .payment_method_data
@@ -344,7 +348,7 @@ pub async fn confirm_subscription(
             &state,
             subscription,
             request.item_price_id,
-            request.billing,
+            request.payment_details.payment_method_data.billing,
         )
         .await?;
 
@@ -359,6 +363,9 @@ pub async fn confirm_subscription(
                 .clone()
                 .and_then(|invoice| invoice.status)
                 .unwrap_or(connector_enums::InvoiceStatus::InvoiceCreated),
+            invoice_details
+                .clone()
+                .map(|invoice| invoice.id.get_string_repr().to_string()),
         )
         .await?;
 
@@ -418,7 +425,7 @@ pub async fn get_subscription(
         .await
         .attach_printable("subscriptions: failed to get subscription entry in get_subscription")?;
 
-    Ok(ApplicationResponse::Json(
-        subscription.to_subscription_response(),
-    ))
+    let response = subscription.to_subscription_response(None, None)?;
+
+    Ok(ApplicationResponse::Json(response))
 }
diff --git a/crates/router/src/core/subscription/invoice_handler.rs b/crates/router/src/core/subscription/invoice_handler.rs
index 8cc026fb373..b7d5cb328fd 100644
--- a/crates/router/src/core/subscription/invoice_handler.rs
+++ b/crates/router/src/core/subscription/invoice_handler.rs
@@ -44,6 +44,7 @@ impl InvoiceHandler {
         status: connector_enums::InvoiceStatus,
         provider_name: connector_enums::Connector,
         metadata: Option<pii::SecretSerdeValue>,
+        connector_invoice_id: Option<String>,
     ) -> errors::RouterResult<diesel_models::invoice::Invoice> {
         let invoice_new = diesel_models::invoice::InvoiceNew::new(
             self.subscription.id.to_owned(),
@@ -58,6 +59,7 @@ impl InvoiceHandler {
             status,
             provider_name,
             metadata,
+            connector_invoice_id,
         );
 
         let invoice = state
@@ -79,10 +81,12 @@ impl InvoiceHandler {
         payment_method_id: Option<Secret<String>>,
         payment_intent_id: Option<common_utils::id_type::PaymentId>,
         status: connector_enums::InvoiceStatus,
+        connector_invoice_id: Option<String>,
     ) -> errors::RouterResult<diesel_models::invoice::Invoice> {
         let update_invoice = diesel_models::invoice::InvoiceUpdate::new(
             payment_method_id.as_ref().map(|id| id.peek()).cloned(),
             Some(status),
+            connector_invoice_id,
             payment_intent_id,
         );
         state
@@ -189,8 +193,8 @@ impl InvoiceHandler {
     ) -> errors::RouterResult<subscription_types::PaymentResponseData> {
         let payment_details = &request.payment_details;
         let cit_payment_request = subscription_types::ConfirmPaymentsRequestData {
-            billing: request.billing.clone(),
-            shipping: request.shipping.clone(),
+            billing: request.payment_details.payment_method_data.billing.clone(),
+            shipping: request.payment_details.shipping.clone(),
             payment_method: payment_details.payment_method,
             payment_method_type: payment_details.payment_method_type,
             payment_method_data: payment_details.payment_method_data.clone(),
diff --git a/crates/router/src/core/subscription/subscription_handler.rs b/crates/router/src/core/subscription/subscription_handler.rs
index b11cf517b03..b608faca5d5 100644
--- a/crates/router/src/core/subscription/subscription_handler.rs
+++ b/crates/router/src/core/subscription/subscription_handler.rs
@@ -19,7 +19,7 @@ use crate::{
     core::{errors::StorageErrorExt, subscription::invoice_handler::InvoiceHandler},
     db::CustomResult,
     routes::SessionState,
-    types::domain,
+    types::{domain, transformers::ForeignTryFrom},
 };
 
 pub struct SubscriptionHandler<'a> {
@@ -227,31 +227,16 @@ impl SubscriptionWithHandler<'_> {
             price_id: None,
             coupon: None,
             billing_processor_subscription_id: self.subscription.connector_subscription_id.clone(),
-            invoice: Some(subscription_types::Invoice {
-                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(),
-            }),
+            invoice: Some(subscription_types::Invoice::foreign_try_from(invoice)?),
         })
     }
 
-    pub fn to_subscription_response(&self) -> SubscriptionResponse {
-        SubscriptionResponse::new(
+    pub fn to_subscription_response(
+        &self,
+        payment: Option<subscription_types::PaymentResponseData>,
+        invoice: Option<&diesel_models::invoice::Invoice>,
+    ) -> errors::RouterResult<SubscriptionResponse> {
+        Ok(SubscriptionResponse::new(
             self.subscription.id.clone(),
             self.subscription.merchant_reference_id.clone(),
             SubscriptionStatus::from_str(&self.subscription.status)
@@ -261,7 +246,15 @@ impl SubscriptionWithHandler<'_> {
             self.subscription.merchant_id.to_owned(),
             self.subscription.client_secret.clone().map(Secret::new),
             self.subscription.customer_id.clone(),
-        )
+            payment,
+            invoice
+                .map(
+                    |invoice| -> errors::RouterResult<subscription_types::Invoice> {
+                        subscription_types::Invoice::foreign_try_from(invoice)
+                    },
+                )
+                .transpose()?,
+        ))
     }
 
     pub async fn update_subscription(
@@ -369,3 +362,30 @@ impl SubscriptionWithHandler<'_> {
         }
     }
 }
+
+impl ForeignTryFrom<&diesel_models::invoice::Invoice> for subscription_types::Invoice {
+    type Error = error_stack::Report<errors::ApiErrorResponse>;
+
+    fn foreign_try_from(invoice: &diesel_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(),
+        })
+    }
+}
diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs
index 657655c25bf..ce183002834 100644
--- a/crates/router/src/core/webhooks/incoming.rs
+++ b/crates/router/src/core/webhooks/incoming.rs
@@ -2645,6 +2645,7 @@ async fn subscription_incoming_webhook_flow(
             InvoiceStatus::PaymentPending,
             connector,
             None,
+            None,
         )
         .await?;
 
@@ -2664,6 +2665,7 @@ async fn subscription_incoming_webhook_flow(
             payment_response.payment_method_id.clone(),
             Some(payment_response.payment_id.clone()),
             InvoiceStatus::from(payment_response.status),
+            Some(mit_payment_data.invoice_id.get_string_repr().to_string()),
         )
         .await?;
 
diff --git a/crates/router/src/routes/subscription.rs b/crates/router/src/routes/subscription.rs
index 46432810450..b1b3397c46c 100644
--- a/crates/router/src/routes/subscription.rs
+++ b/crates/router/src/routes/subscription.rs
@@ -101,16 +101,25 @@ pub async fn confirm_subscription(
 ) -> impl Responder {
     let flow = Flow::ConfirmSubscription;
     let subscription_id = subscription_id.into_inner();
+    let payload = json_payload.into_inner();
     let profile_id = match extract_profile_id(&req) {
         Ok(id) => id,
         Err(response) => return response,
     };
 
+    let api_auth = auth::ApiKeyAuth::default();
+
+    let (auth_type, _) =
+        match auth::check_client_secret_and_get_auth(req.headers(), &payload, api_auth) {
+            Ok(auth) => auth,
+            Err(err) => return oss_api::log_and_return_error_response(error_stack::report!(err)),
+        };
+
     Box::pin(oss_api::server_wrap(
         flow,
         state,
         &req,
-        json_payload.into_inner(),
+        payload,
         |state, auth: auth::AuthenticationData, payload, _| {
             let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                 domain::Context(auth.merchant_account, auth.key_store),
@@ -124,10 +133,7 @@ pub async fn confirm_subscription(
             )
         },
         auth::auth_type(
-            &auth::HeaderAuth(auth::ApiKeyAuth {
-                is_connected_allowed: false,
-                is_platform_allowed: false,
-            }),
+            &*auth_type,
             &auth::JWTAuth {
                 permission: Permission::ProfileSubscriptionWrite,
             },
@@ -146,28 +152,36 @@ pub async fn get_subscription_plans(
 ) -> impl Responder {
     let flow = Flow::GetPlansForSubscription;
     let api_auth = auth::ApiKeyAuth::default();
+    let payload = query.into_inner();
 
     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),
-    };
+    let (auth_type, _) =
+        match auth::check_client_secret_and_get_auth(req.headers(), &payload, api_auth) {
+            Ok(auth) => auth,
+            Err(err) => return oss_api::log_and_return_error_response(error_stack::report!(err)),
+        };
     Box::pin(oss_api::server_wrap(
         flow,
         state,
         &req,
-        query.into_inner(),
+        payload,
         |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,
+        auth::auth_type(
+            &*auth_type,
+            &auth::JWTAuth {
+                permission: Permission::ProfileSubscriptionRead,
+            },
+            req.headers(),
+        ),
         api_locking::LockAction::NotApplicable,
     ))
     .await
@@ -186,6 +200,7 @@ pub async fn get_subscription(
         Ok(id) => id,
         Err(response) => return response,
     };
+
     Box::pin(oss_api::server_wrap(
         flow,
         state,
@@ -244,10 +259,16 @@ pub async fn create_and_confirm_subscription(
                 payload.clone(),
             )
         },
-        &auth::HeaderAuth(auth::ApiKeyAuth {
-            is_connected_allowed: false,
-            is_platform_allowed: false,
-        }),
+        auth::auth_type(
+            &auth::HeaderAuth(auth::ApiKeyAuth {
+                is_connected_allowed: false,
+                is_platform_allowed: false,
+            }),
+            &auth::JWTAuth {
+                permission: Permission::ProfileSubscriptionWrite,
+            },
+            req.headers(),
+        ),
         api_locking::LockAction::NotApplicable,
     ))
     .await
diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs
index 13d224a1cd7..4bbb1813833 100644
--- a/crates/router/src/services/authentication.rs
+++ b/crates/router/src/services/authentication.rs
@@ -4326,6 +4326,20 @@ impl ClientSecretFetch for api_models::payment_methods::PaymentMethodUpdate {
     }
 }
 
+#[cfg(feature = "v1")]
+impl ClientSecretFetch for api_models::subscription::ConfirmSubscriptionRequest {
+    fn get_client_secret(&self) -> Option<&String> {
+        self.client_secret.as_ref().map(|s| s.as_string())
+    }
+}
+
+#[cfg(feature = "v1")]
+impl ClientSecretFetch for api_models::subscription::GetPlansQuery {
+    fn get_client_secret(&self) -> Option<&String> {
+        self.client_secret.as_ref().map(|s| s.as_string())
+    }
+}
+
 #[cfg(feature = "v1")]
 impl ClientSecretFetch for api_models::authentication::AuthenticationEligibilityRequest {
     fn get_client_secret(&self) -> Option<&String> {
diff --git a/crates/router/src/workflows/invoice_sync.rs b/crates/router/src/workflows/invoice_sync.rs
index c6daab02116..ec71af2b708 100644
--- a/crates/router/src/workflows/invoice_sync.rs
+++ b/crates/router/src/workflows/invoice_sync.rs
@@ -204,6 +204,7 @@ impl<'a> InvoiceSyncHandler<'a> {
                 None,
                 None,
                 common_enums::connector_enums::InvoiceStatus::from(invoice_sync_status),
+                Some(connector_invoice_id),
             )
             .await
             .attach_printable("Failed to update invoice in DB")?;
diff --git a/migrations/2025-10-07-130304_add_connector_invoice_id/down.sql b/migrations/2025-10-07-130304_add_connector_invoice_id/down.sql
new file mode 100644
index 00000000000..85f8bcf59e5
--- /dev/null
+++ b/migrations/2025-10-07-130304_add_connector_invoice_id/down.sql
@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+ALTER TABLE invoice DROP COLUMN IF EXISTS connector_invoice_id;
+DROP INDEX IF EXISTS invoice_subscription_id_connector_invoice_id_index;
\ No newline at end of file
diff --git a/migrations/2025-10-07-130304_add_connector_invoice_id/up.sql b/migrations/2025-10-07-130304_add_connector_invoice_id/up.sql
new file mode 100644
index 00000000000..d17ca57da77
--- /dev/null
+++ b/migrations/2025-10-07-130304_add_connector_invoice_id/up.sql
@@ -0,0 +1,3 @@
+-- Your SQL goes here
+ALTER TABLE invoice ADD COLUMN IF NOT EXISTS connector_invoice_id VARCHAR(64);
+CREATE INDEX invoice_subscription_id_connector_invoice_id_index ON invoice (subscription_id, connector_invoice_id);
\ No newline at end of file

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This pull request introduces several enhancements and refactors to the subscription API, focusing on improving authentication, response structure, and data handling. The main changes include adding payment and invoice details to subscription responses, updating authentication logic to support client secrets in queries, and refactoring handler methods for better error handling and extensibility.

**Subscription Response Improvements**
* Added `payment` and `invoice` fields to the `SubscriptionResponse` struct to provide more detailed information about payments and invoices associated with a subscription.
* Updated the `PaymentResponseData` struct to use a `Secret<String>` for the `client_secret` field, improving security and consistency.

**Authentication and Query Handling**
* Introduced a new `GetSubscriptionQuery` struct to allow passing a client secret when fetching subscription details, and implemented `ClientSecretFetch` for relevant subscription request/query types. [[1]](diffhunk://#diff-3d85d9e288cc67856867986ee406a3102ce1d0cf9fe5c8f9efed3a578608f817R206-R215) [[2]](diffhunk://#diff-c66a703d6e518900240c7296a4d6f9c55c9787ea8f490a419dd0395dc0b36d70R4329-R4349)
* Refactored authentication logic in subscription route handlers (`get_subscription`, `get_subscription_plans`, and `confirm_subscription`) to extract and validate client secrets from query payloads, improving security and flexibility. [[1]](diffhunk://#diff-d737ada25c830476b3867c693bd362d4b1142773f1b0d971556518b5c04691a3R104-R122) [[2]](diffhunk://#diff-d737ada25c830476b3867c693bd362d4b1142773f1b0d971556518b5c04691a3R149-R172) [[3]](diffhunk://#diff-d737ada25c830476b3867c693bd362d4b1142773f1b0d971556518b5c04691a3R184-R199) [[4]](diffhunk://#diff-d737ada25c830476b3867c693bd362d4b1142773f1b0d971556518b5c04691a3L205-R216)

**Handler and Response Refactoring**
* Refactored the `to_subscription_response` method in `SubscriptionWithHandler` to accept optional payment and invoice parameters, and handle invoice conversion with error reporting for invalid currency values. [[1]](diffhunk://#diff-1d71cc03e82ba9b3c7478b6304bd763431e20cc723881a1623ab3127301b8976L253-R258) [[2]](diffhunk://#diff-1d71cc03e82ba9b3c7478b6304bd763431e20cc723881a1623ab3127301b8976R268-R295)
* Updated core subscription logic to use the new response structure and refactored method signatures for consistency, including changes to how responses are generated and returned. [[1]](diffhunk://#diff-adea1b33060886c62616ea56c46edb8f356525cbacea004da12e8819334cf27cL34-R31) [[2]](diffhunk://#diff-adea1b33060886c62616ea56c46edb8f356525cbacea004da12e8819334cf27cL71-R68) [[3]](diffhunk://#diff-adea1b33060886c62616ea56c46edb8f356525cbacea004da12e8819334cf27cL94-R91) [[4]](diffhunk://#diff-adea1b33060886c62616ea56c46edb8f356525cbacea004da12e8819334cf27cL420-R412)

These changes collectively improve the robustness and extensibility of the subscription API, making it easier to integrate payment and invoice details while strengthening authentication mechanisms.


### Additional Changes

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

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## 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. Subscription create
```
curl --location 'http://localhost:8080/subscriptions/create' \
--header 'Content-Type: application/json' \
--header 'X-Profile-Id: pro_CohRshEanxkKSMHgyu6a' \
--header 'api-key: dev_tV0O30052zhBCqdrhGMfQxMN0DerXwnMkNHX6ndCbs0BovewetTaXznBQOCLGAi2' \
--data '{
    "customer_id": "cus_d64F9yomaDMqbTDlxw3g",
    "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_8zxmAZAfS0eTL8zRJRk7","merchant_reference_id":null,"status":"created","plan_id":null,"profile_id":"pro_CohRshEanxkKSMHgyu6a","client_secret":"sub_8zxmAZAfS0eTL8zRJRk7_secret_IGg9yCNzrzoziV3odFLt","merchant_id":"merchant_1759849271","coupon_code":null,"customer_id":"cus_d64F9yomaDMqbTDlxw3g","payment":{"payment_id":"pay_pPn6Tz2m3WrOIzANMF2g","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_pPn6Tz2m3WrOIzANMF2g_secret_Wxi6r52cBxz0ujhcWpAh"},"invoice":{"id":"invoice_nEqu7GMVqNMRzBorC9BS","subscription_id":"sub_8zxmAZAfS0eTL8zRJRk7","merchant_id":"merchant_1759849271","profile_id":"pro_CohRshEanxkKSMHgyu6a","merchant_connector_id":"mca_S0OzkqOOhMZs2jJQPSZo","payment_intent_id":"pay_pPn6Tz2m3WrOIzANMF2g","payment_method_id":null,"customer_id":"cus_d64F9yomaDMqbTDlxw3g","amount":14100,"currency":"USD","status":"invoice_created"}}
```

2. Get plans
```
curl --location 'http://localhost:8080/subscriptions/plans?limit=2&offset=0&client_secret=sub_8zxmAZAfS0eTL8zRJRk7_secret_IGg9yCNzrzoziV3odFLt' \
--header 'Content-Type: application/json' \
--header 'api-key: pk_dev_febeb0fab3be43c79024efaf99764d63' \
--header 'X-Profile-Id: pro_CohRshEanxkKSMHgyu6a'
```

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}]}]
```

3. Confirm subscription - 
```
curl --location 'http://localhost:8080/subscriptions/sub_8zxmAZAfS0eTL8zRJRk7/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'X-Profile-Id: pro_CohRshEanxkKSMHgyu6a' \
--header 'api-key: pk_dev_febeb0fab3be43c79024efaf99764d63' \
--data '{
    "item_price_id": "cbdemo_enterprise-suite-monthly",
    "description": "Hello this is description",
    "client_secret": "sub_8zxmAZAfS0eTL8zRJRk7_secret_IGg9yCNzrzoziV3odFLt",
    "shipping": {
        "address": {
            "state": "zsaasdas",
            "city": "Banglore",
            "country": "US",
            "line1": "sdsdfsdf",
            "line2": "hsgdbhd",
            "line3": "alsksoe",
            "zip": "571201",
            "first_name": "joseph",
            "last_name": "doe"
        },
        "phone": {
            "number": "123456789",
            "country_code": "+1"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "123456789",
            "country_code": "+1"
        }
    },
    "payment_details": {
        "payment_method": "card",
        "payment_method_type": "credit",
        "payment_method_data": {
            "card": {
                "card_number": "4111111111111111",
                "card_exp_month": "03",
                "card_exp_year": "2030",
                "card_holder_name": "CLBRW dffdg",
                "card_cvc": "737"
            }
        },
        "customer_acceptance": {
            "acceptance_type": "online",
            "accepted_at": "1963-05-03T04:07:52.723Z",
            "online": {
                "ip_address": "127.0.0.1",
                "user_agent": "amet irure esse"
            }
        }
    }
}'
```

Response - 
```
{"id":"sub_8zxmAZAfS0eTL8zRJRk7","merchant_reference_id":null,"status":"active","plan_id":null,"price_id":null,"coupon":null,"profile_id":"pro_CohRshEanxkKSMHgyu6a","payment":{"payment_id":"pay_pPn6Tz2m3WrOIzANMF2g","status":"succeeded","amount":14100,"currency":"USD","connector":"stripe","payment_method_id":"pm_ZAtcND5u4SoRq6DnTQET","payment_experience":null,"error_code":null,"error_message":null,"payment_method_type":"credit","client_secret":"pay_pPn6Tz2m3WrOIzANMF2g_secret_Wxi6r52cBxz0ujhcWpAh"},"customer_id":"cus_d64F9yomaDMqbTDlxw3g","invoice":{"id":"invoice_nEqu7GMVqNMRzBorC9BS","subscription_id":"sub_8zxmAZAfS0eTL8zRJRk7","merchant_id":"merchant_1759849271","profile_id":"pro_CohRshEanxkKSMHgyu6a","merchant_connector_id":"mca_S0OzkqOOhMZs2jJQPSZo","payment_intent_id":"pay_pPn6Tz2m3WrOIzANMF2g","payment_method_id":"pm_ZAtcND5u4SoRq6DnTQET","customer_id":"cus_d64F9yomaDMqbTDlxw3g","amount":14100,"currency":"USD","status":"payment_pending"},"billing_processor_subscription_id":"sub_8zxmAZAfS0eTL8zRJRk7"}
```

invoice - 
<img width="942" height="409" alt="image" src="https://github.com/user-attachments/assets/c674745e-428d-4a69-9c74-4732e160fb31" />


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

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