Bug: [feature] add mit payment s2s call and invoice sync job to subscription webhook



Create Mit payment handler in subscription invoice_handler.rs
and add create_invoice_sync_job after updating the invoice with the payment ID 
diff --git a/crates/api_models/src/subscription.rs b/crates/api_models/src/subscription.rs
index 9378d7796f4..2829d58b883 100644
--- a/crates/api_models/src/subscription.rs
+++ b/crates/api_models/src/subscription.rs
@@ -5,6 +5,7 @@ use utoipa::ToSchema;
 
 use crate::{
     enums as api_enums,
+    mandates::RecurringDetails,
     payments::{Address, PaymentMethodDataRequest},
 };
 
@@ -279,6 +280,17 @@ pub struct PaymentResponseData {
     pub payment_method_type: Option<api_enums::PaymentMethodType>,
     pub client_secret: Option<String>,
 }
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
+pub struct CreateMitPaymentRequestData {
+    pub amount: MinorUnit,
+    pub currency: api_enums::Currency,
+    pub confirm: bool,
+    pub customer_id: Option<common_utils::id_type::CustomerId>,
+    pub recurring_details: Option<RecurringDetails>,
+    pub off_session: Option<bool>,
+}
+
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
 pub struct ConfirmSubscriptionRequest {
     /// Client secret for SDK based interaction.
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index b1859cc6c28..b9053b46bc5 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
 pub use ui::*;
 use utoipa::ToSchema;
 
-pub use super::connector_enums::RoutableConnectors;
+pub use super::connector_enums::{InvoiceStatus, RoutableConnectors};
 #[doc(hidden)]
 pub mod diesel_exports {
     pub use super::{
@@ -9598,3 +9598,23 @@ pub enum GooglePayCardFundingSource {
     #[serde(other)]
     Unknown,
 }
+
+impl From<IntentStatus> for InvoiceStatus {
+    fn from(value: IntentStatus) -> Self {
+        match value {
+            IntentStatus::Succeeded => Self::InvoicePaid,
+            IntentStatus::RequiresCapture
+            | IntentStatus::PartiallyCaptured
+            | IntentStatus::PartiallyCapturedAndCapturable
+            | IntentStatus::PartiallyAuthorizedAndRequiresCapture
+            | IntentStatus::Processing
+            | IntentStatus::RequiresCustomerAction
+            | IntentStatus::RequiresConfirmation
+            | IntentStatus::RequiresPaymentMethod => Self::PaymentPending,
+            IntentStatus::RequiresMerchantAction => Self::ManualReview,
+            IntentStatus::Cancelled | IntentStatus::CancelledPostCapture => Self::PaymentCanceled,
+            IntentStatus::Expired => Self::PaymentPendingTimeout,
+            IntentStatus::Failed | IntentStatus::Conflicted => Self::PaymentFailed,
+        }
+    }
+}
diff --git a/crates/diesel_models/src/invoice.rs b/crates/diesel_models/src/invoice.rs
index 8ffd54c288d..59ef0b272d5 100644
--- a/crates/diesel_models/src/invoice.rs
+++ b/crates/diesel_models/src/invoice.rs
@@ -57,6 +57,7 @@ pub struct InvoiceUpdate {
     pub status: Option<String>,
     pub payment_method_id: Option<String>,
     pub modified_at: time::PrimitiveDateTime,
+    pub payment_intent_id: Option<common_utils::id_type::PaymentId>,
 }
 
 impl InvoiceNew {
@@ -98,10 +99,15 @@ impl InvoiceNew {
 }
 
 impl InvoiceUpdate {
-    pub fn new(payment_method_id: Option<String>, status: Option<InvoiceStatus>) -> Self {
+    pub fn new(
+        payment_method_id: Option<String>,
+        status: Option<InvoiceStatus>,
+        payment_intent_id: Option<common_utils::id_type::PaymentId>,
+    ) -> Self {
         Self {
             payment_method_id,
             status: status.map(|status| status.to_string()),
+            payment_intent_id,
             modified_at: common_utils::date_time::now(),
         }
     }
diff --git a/crates/router/src/core/subscription.rs b/crates/router/src/core/subscription.rs
index 1ec8514fc65..2d3fd649669 100644
--- a/crates/router/src/core/subscription.rs
+++ b/crates/router/src/core/subscription.rs
@@ -354,6 +354,7 @@ pub async fn confirm_subscription(
             &state,
             invoice.id,
             payment_response.payment_method_id.clone(),
+            Some(payment_response.payment_id.clone()),
             invoice_details
                 .clone()
                 .and_then(|invoice| invoice.status)
diff --git a/crates/router/src/core/subscription/invoice_handler.rs b/crates/router/src/core/subscription/invoice_handler.rs
index 3809474acff..8cc026fb373 100644
--- a/crates/router/src/core/subscription/invoice_handler.rs
+++ b/crates/router/src/core/subscription/invoice_handler.rs
@@ -77,11 +77,13 @@ impl InvoiceHandler {
         state: &SessionState,
         invoice_id: common_utils::id_type::InvoiceId,
         payment_method_id: Option<Secret<String>>,
+        payment_intent_id: Option<common_utils::id_type::PaymentId>,
         status: connector_enums::InvoiceStatus,
     ) -> 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),
+            payment_intent_id,
         );
         state
             .store
@@ -255,4 +257,31 @@ impl InvoiceHandler {
             .attach_printable("invoices: unable to create invoice sync job in database")?;
         Ok(())
     }
+
+    pub async fn create_mit_payment(
+        &self,
+        state: &SessionState,
+        amount: MinorUnit,
+        currency: common_enums::Currency,
+        payment_method_id: &str,
+    ) -> errors::RouterResult<subscription_types::PaymentResponseData> {
+        let mit_payment_request = subscription_types::CreateMitPaymentRequestData {
+            amount,
+            currency,
+            confirm: true,
+            customer_id: Some(self.subscription.customer_id.clone()),
+            recurring_details: Some(api_models::mandates::RecurringDetails::PaymentMethodId(
+                payment_method_id.to_owned(),
+            )),
+            off_session: Some(true),
+        };
+
+        payments_api_client::PaymentsApiClient::create_mit_payment(
+            state,
+            mit_payment_request,
+            self.merchant_account.get_id().get_string_repr(),
+            self.profile.get_id().get_string_repr(),
+        )
+        .await
+    }
 }
diff --git a/crates/router/src/core/subscription/payments_api_client.rs b/crates/router/src/core/subscription/payments_api_client.rs
index e483469be96..e530a5843c8 100644
--- a/crates/router/src/core/subscription/payments_api_client.rs
+++ b/crates/router/src/core/subscription/payments_api_client.rs
@@ -192,4 +192,27 @@ impl PaymentsApiClient {
         )
         .await
     }
+
+    pub async fn create_mit_payment(
+        state: &SessionState,
+        request: subscription_types::CreateMitPaymentRequestData,
+        merchant_id: &str,
+        profile_id: &str,
+    ) -> errors::RouterResult<subscription_types::PaymentResponseData> {
+        let base_url = &state.conf.internal_services.payments_base_url;
+        let url = format!("{}/payments", base_url);
+
+        Self::make_payment_api_call(
+            state,
+            services::Method::Post,
+            url,
+            Some(common_utils::request::RequestContent::Json(Box::new(
+                request,
+            ))),
+            "Create MIT Payment",
+            merchant_id,
+            profile_id,
+        )
+        .await
+    }
 }
diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs
index f207c243d26..657655c25bf 100644
--- a/crates/router/src/core/webhooks/incoming.rs
+++ b/crates/router/src/core/webhooks/incoming.rs
@@ -2622,7 +2622,7 @@ async fn subscription_incoming_webhook_flow(
         .await
         .attach_printable("subscriptions: failed to get subscription entry in get_subscription")?;
 
-    let invoice_handler = subscription_with_handler.get_invoice_handler(profile);
+    let invoice_handler = subscription_with_handler.get_invoice_handler(profile.clone());
 
     let payment_method_id = subscription_with_handler
         .subscription
@@ -2635,7 +2635,7 @@ async fn subscription_incoming_webhook_flow(
 
     logger::info!("Payment method ID found: {}", payment_method_id);
 
-    let _invoice_new = invoice_handler
+    let invoice_entry = invoice_handler
         .create_invoice_entry(
             &state,
             billing_connector_mca_id.clone(),
@@ -2648,5 +2648,33 @@ async fn subscription_incoming_webhook_flow(
         )
         .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 updated_invoice = invoice_handler
+        .update_invoice(
+            &state,
+            invoice_entry.id.clone(),
+            payment_response.payment_method_id.clone(),
+            Some(payment_response.payment_id.clone()),
+            InvoiceStatus::from(payment_response.status),
+        )
+        .await?;
+
+    invoice_handler
+        .create_invoice_sync_job(
+            &state,
+            &updated_invoice,
+            mit_payment_data.invoice_id.get_string_repr().to_string(),
+            connector,
+        )
+        .await?;
+
     Ok(WebhookResponseTracker::NoEffect)
 }
diff --git a/crates/router/src/workflows/invoice_sync.rs b/crates/router/src/workflows/invoice_sync.rs
index ec2d2d7a913..c6daab02116 100644
--- a/crates/router/src/workflows/invoice_sync.rs
+++ b/crates/router/src/workflows/invoice_sync.rs
@@ -202,6 +202,7 @@ impl<'a> InvoiceSyncHandler<'a> {
                 self.state,
                 self.invoice.id.to_owned(),
                 None,
+                None,
                 common_enums::connector_enums::InvoiceStatus::from(invoice_sync_status),
             )
             .await

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

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

## Description
<!-- Describe your changes in detail -->

This pr completes [9400](https://github.com/juspay/hyperswitch/pull/9400)


This pr adds support to do MIT payment for subscription and adds an invoice sync job 
Created Mit payment handler in subscription invoice_handler.rs
and added create_invoice_sync_job after updating the invoice with the payment ID


### 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).
-->
```
Enable internal API key Auth by changing config/development.toml
[internal_merchant_id_profile_id_auth]
enabled = true
internal_api_key = "test_internal_api_key"
```
1. Create Merchant, API key, Payment connector, Billing Connector
2. Update profile to set the billing processor
Request
```
curl --location 'http://localhost:8080/account/merchant_1759125756/business_profile/pro_71jcERFiZERp44d8fuSJ' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_0RJW0cwZDeGfSkod0eJWOkfZHK0PCoZtDYJJ1FxeHqhHky1VNPU6nkgFQiTFSDvA' \
--data '{
  "billing_processor_id": "mca_NLnCEvS2DfuHWHFVa09o"
}'
```
Response
```
{"merchant_id":"merchant_1759125756","profile_id":"pro_71jcERFiZERp44d8fuSJ","profile_name":"US_default","return_url":"https://google.com/success","enable_payment_response_hash":true,"payment_response_hash_key":"ZFjTKSE1EYiXaPcRhacrwlNh4wieUorlmP7r2uSHI8fyHigGmNCKY2hqVtzAq51m","redirect_to_merchant_with_http_post":false,"webhook_details":{"webhook_version":"1.0.1","webhook_username":"ekart_retail","webhook_password":"password_ekart@123","webhook_url":null,"payment_created_enabled":true,"payment_succeeded_enabled":true,"payment_failed_enabled":true,"payment_statuses_enabled":null,"refund_statuses_enabled":null,"payout_statuses_enabled":null},"metadata":null,"routing_algorithm":null,"intent_fulfillment_time":900,"frm_routing_algorithm":null,"payout_routing_algorithm":null,"applepay_verified_domains":null,"session_expiry":900,"payment_link_config":null,"authentication_connector_details":null,"use_billing_as_payment_method_billing":true,"extended_card_info_config":null,"collect_shipping_details_from_wallet_connector":false,"collect_billing_details_from_wallet_connector":false,"always_collect_shipping_details_from_wallet_connector":false,"always_collect_billing_details_from_wallet_connector":false,"is_connector_agnostic_mit_enabled":false,"payout_link_config":null,"outgoing_webhook_custom_http_headers":null,"tax_connector_id":null,"is_tax_connector_enabled":false,"is_network_tokenization_enabled":false,"is_auto_retries_enabled":false,"max_auto_retries_enabled":null,"always_request_extended_authorization":null,"is_click_to_pay_enabled":false,"authentication_product_ids":null,"card_testing_guard_config":{"card_ip_blocking_status":"disabled","card_ip_blocking_threshold":3,"guest_user_card_blocking_status":"disabled","guest_user_card_blocking_threshold":10,"customer_id_blocking_status":"disabled","customer_id_blocking_threshold":5,"card_testing_guard_expiry":3600},"is_clear_pan_retries_enabled":false,"force_3ds_challenge":false,"is_debit_routing_enabled":false,"merchant_business_country":null,"is_pre_network_tokenization_enabled":false,"acquirer_configs":null,"is_iframe_redirection_enabled":null,"merchant_category_code":null,"merchant_country_code":null,"dispute_polling_interval":null,"is_manual_retry_enabled":null,"always_enable_overcapture":null,"is_external_vault_enabled":"skip","external_vault_connector_details":null,"billing_processor_id":"mca_NLnCEvS2DfuHWHFVa09o"}
```
3. Create Customer
Request
```
curl --location 'http://localhost:8080/customers' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_0RJW0cwZDeGfSkod0eJWOkfZHK0PCoZtDYJJ1FxeHqhHky1VNPU6nkgFQiTFSDvA' \
--data-raw '{
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "First customer",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'
```
Response
```
{
    "customer_id": "cus_OYy3hzTTt04Enegu3Go3",
    "name": "John Doe",
    "email": "guest@example.com",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "First customer",
    "address": null,
    "created_at": "2025-09-29T14:45:14.430Z",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "default_payment_method_id": null,
    "tax_registration_id": null
}
```

7. Subscription create and confirm 
Request
```
curl --location 'http://localhost:8080/subscriptions' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'X-Profile-Id: pro_MiKOgwbj84jJ095MoWdj' \
--header 'api-key: dev_I62cGvBVijteiN61aMcAOeYvXpO8s36gT8BFvPznYYDXF4GFtVtjzgiel9oIpvUK' \
--data '{
    "item_price_id": "cbdemo_enterprise-suite-monthly",
    "customer_id": "cus_efEv4nqfrYEo8wSKZ10C",
    "description": "Hello this is description",
    "merchant_reference_id": "mer_ref_1759334677",
    "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"
            }
        },
        "authentication_type": "no_three_ds",
        "setup_future_usage": "off_session",
        "capture_method": "automatic",
        "return_url": "https://google.com",
        "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_CkPzNaFAyxzX2WwdKFCr",
    "merchant_reference_id": "mer_ref_1759334529",
    "status": "active",
    "plan_id": null,
    "price_id": null,
    "coupon": null,
    "profile_id": "pro_MiKOgwbj84jJ095MoWdj",
    "payment": {
        "payment_id": "sub_pay_KXEtoTxvu0EsadB5wxNn",
        "status": "succeeded",
        "amount": 14100,
        "currency": "INR",
        "connector": "stripe",
        "payment_method_id": "pm_bDPHgNaP7UZGjtZ7QPrS",
        "payment_experience": null,
        "error_code": null,
        "error_message": null
    },
    "customer_id": "cus_uBtUJLSVSICr8ctmoL8i",
    "invoice": {
        "id": "invoice_Ht72m0Enqy4YOGRKAtbZ",
        "subscription_id": "sub_CkPzNaFAyxzX2WwdKFCr",
        "merchant_id": "merchant_1759157014",
        "profile_id": "pro_MiKOgwbj84jJ095MoWdj",
        "merchant_connector_id": "mca_oMyzcBFcpfISftwDDVuG",
        "payment_intent_id": "sub_pay_KXEtoTxvu0EsadB5wxNn",
        "payment_method_id": null,
        "customer_id": "cus_uBtUJLSVSICr8ctmoL8i",
        "amount": 14100,
        "currency": "INR",
        "status": "payment_pending"
    },
    "billing_processor_subscription_id": "sub_CkPzNaFAyxzX2WwdKFCr"
}
```

incoming webhook for chargebee
```
curl --location 'http://localhost:8080/webhooks/merchant_1758710612/mca_YH3XxYGMv6IbKD0icBie' \
--header 'api-key: dev_5LNrrMNqyyFle8hvMssLob9pFq6OIEh0Vl3DqZttFXiDe7wrrScUePEYrIiYfSKT' \
--header 'Content-Type: application/json' \
--header 'authorization: Basic aHlwZXJzd2l0Y2g6aHlwZXJzd2l0Y2g=' \
--data-raw '{
    "api_version": "v2",
    "content": {
        "invoice": {
            "adjustment_credit_notes": [],
            "amount_adjusted": 0,
            "amount_due": 14100,
            "amount_paid": 0,
            "amount_to_collect": 14100,
            "applied_credits": [],
            "base_currency_code": "INR",
            "channel": "web",
            "credits_applied": 0,
            "currency_code": "INR",
            "customer_id": "gaurav_test",
            "date": 1758711043,
            "deleted": false,
            "due_date": 1758711043,
            "dunning_attempts": [],
            "exchange_rate": 1.0,
            "first_invoice": false,
            "generated_at": 1758711043,
            "has_advance_charges": false,
            "id": "3",
            "is_gifted": false,
            "issued_credit_notes": [],
            "line_items": [
                {
                    "amount": 14100,
                    "customer_id": "gaurav_test",
                    "date_from": 1758711043,
                    "date_to": 1761303043,
                    "description": "Enterprise Suite Monthly",
                    "discount_amount": 0,
                    "entity_id": "cbdemo_enterprise-suite-monthly",
                    "entity_type": "plan_item_price",
                    "id": "li_169vD0Uxi8JBp46g",
                    "is_taxed": false,
                    "item_level_discount_amount": 0,
                    "object": "line_item",
                    "pricing_model": "flat_fee",
                    "quantity": 1,
                    "subscription_id": "169vD0Uxi8JB746e",
                    "tax_amount": 0,
                    "tax_exempt_reason": "tax_not_configured",
                    "unit_amount": 14100
                }
            ],
            "linked_orders": [],
            "linked_payments": [],
            "net_term_days": 0,
            "new_sales_amount": 14100,
            "object": "invoice",
            "price_type": "tax_exclusive",
            "recurring": true,
            "reference_transactions": [],
            "resource_version": 1758711043846,
            "round_off_amount": 0,
            "site_details_at_creation": {
                "timezone": "Asia/Calcutta"
            },
            "status": "payment_due",
            "sub_total": 14100,
            "subscription_id": "169vD0Uxi8JB746e",
            "tax": 0,
            "term_finalized": true,
            "total": 14100,
            "updated_at": 1758711043,
            "write_off_amount": 0
        }
    },
    "event_type": "invoice_generated",
    "id": "ev_169vD0Uxi8JDf46i",
    "object": "event",
    "occurred_at": 1758711043,
    "source": "admin_console",
    "user": "ankit.gupta.001@juspay.in",
    "webhook_status": "scheduled",
    "webhooks": [
        {
            "id": "whv2_169mYOUxi3nvkZ8v",
            "object": "webhook",
            "webhook_status": "scheduled"
        }
    ]
}'
```

response

200 ok 

## 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)?
-->

invoice sync finished
<img width="1504" height="463" alt="image" src="https://github.com/user-attachments/assets/13e1469d-bb39-4484-b350-5ee278c66a3a" />
Record back successful
<img width="1200" height="115" alt="image" src="https://github.com/user-attachments/assets/d221b8da-44dc-4790-86be-70dfd9f601df" />
invoice table entry
<img width="668" height="402" alt="image" src="https://github.com/user-attachments/assets/29e59bea-dd08-4ae1-8e88-495abdaf8735" />



## 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`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
