Bug: Add an api to toggle `connector_agnostic_mit` feature



Add api to toggle `connector_agnostic_mit` to enable usage of Network Transaction/Reference ID in recurring mandate payments. This will be a business profile level feature that a merchant specifies.

diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs
index fde693a3700..75bd2a31d8e 100644
--- a/crates/api_models/src/admin.rs
+++ b/crates/api_models/src/admin.rs
@@ -1100,6 +1100,13 @@ pub struct ExtendedCardInfoChoice {
 
 impl common_utils::events::ApiEventMetric for ExtendedCardInfoChoice {}
 
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
+pub struct ConnectorAgnosticMitChoice {
+    pub enabled: bool,
+}
+
+impl common_utils::events::ApiEventMetric for ConnectorAgnosticMitChoice {}
+
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
 pub struct ExtendedCardInfoConfig {
     /// Merchant public key
diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs
index 90a9dcbb7d8..1131e9ad674 100644
--- a/crates/diesel_models/src/business_profile.rs
+++ b/crates/diesel_models/src/business_profile.rs
@@ -37,6 +37,7 @@ pub struct BusinessProfile {
     pub authentication_connector_details: Option<serde_json::Value>,
     pub is_extended_card_info_enabled: Option<bool>,
     pub extended_card_info_config: Option<pii::SecretSerdeValue>,
+    pub is_connector_agnostic_mit_enabled: Option<bool>,
 }
 
 #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
@@ -65,6 +66,7 @@ pub struct BusinessProfileNew {
     pub authentication_connector_details: Option<serde_json::Value>,
     pub is_extended_card_info_enabled: Option<bool>,
     pub extended_card_info_config: Option<pii::SecretSerdeValue>,
+    pub is_connector_agnostic_mit_enabled: Option<bool>,
 }
 
 #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
@@ -90,6 +92,7 @@ pub struct BusinessProfileUpdateInternal {
     pub authentication_connector_details: Option<serde_json::Value>,
     pub is_extended_card_info_enabled: Option<bool>,
     pub extended_card_info_config: Option<pii::SecretSerdeValue>,
+    pub is_connector_agnostic_mit_enabled: Option<bool>,
 }
 
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -117,6 +120,9 @@ pub enum BusinessProfileUpdate {
     ExtendedCardInfoUpdate {
         is_extended_card_info_enabled: Option<bool>,
     },
+    ConnectorAgnosticMitUpdate {
+        is_connector_agnostic_mit_enabled: Option<bool>,
+    },
 }
 
 impl From<BusinessProfileUpdate> for BusinessProfileUpdateInternal {
@@ -168,6 +174,12 @@ impl From<BusinessProfileUpdate> for BusinessProfileUpdateInternal {
                 is_extended_card_info_enabled,
                 ..Default::default()
             },
+            BusinessProfileUpdate::ConnectorAgnosticMitUpdate {
+                is_connector_agnostic_mit_enabled,
+            } => Self {
+                is_connector_agnostic_mit_enabled,
+                ..Default::default()
+            },
         }
     }
 }
@@ -195,6 +207,7 @@ impl From<BusinessProfileNew> for BusinessProfile {
             payment_link_config: new.payment_link_config,
             session_expiry: new.session_expiry,
             authentication_connector_details: new.authentication_connector_details,
+            is_connector_agnostic_mit_enabled: new.is_connector_agnostic_mit_enabled,
             is_extended_card_info_enabled: new.is_extended_card_info_enabled,
             extended_card_info_config: new.extended_card_info_config,
         }
@@ -223,6 +236,7 @@ impl BusinessProfileUpdate {
             authentication_connector_details,
             is_extended_card_info_enabled,
             extended_card_info_config,
+            is_connector_agnostic_mit_enabled,
         } = self.into();
         BusinessProfile {
             profile_name: profile_name.unwrap_or(source.profile_name),
@@ -245,6 +259,7 @@ impl BusinessProfileUpdate {
             session_expiry,
             authentication_connector_details,
             is_extended_card_info_enabled,
+            is_connector_agnostic_mit_enabled,
             extended_card_info_config,
             ..source
         }
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index f5dc5abe1e6..2e28bc5d30f 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -194,6 +194,7 @@ diesel::table! {
         authentication_connector_details -> Nullable<Jsonb>,
         is_extended_card_info_enabled -> Nullable<Bool>,
         extended_card_info_config -> Nullable<Jsonb>,
+        is_connector_agnostic_mit_enabled -> Nullable<Bool>,
     }
 }
 
diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs
index 7d2fabc3f37..0b8d52332d9 100644
--- a/crates/router/src/core/admin.rs
+++ b/crates/router/src/core/admin.rs
@@ -1739,6 +1739,47 @@ pub async fn extended_card_info_toggle(
     Ok(service_api::ApplicationResponse::Json(ext_card_info_choice))
 }
 
+pub async fn connector_agnostic_mit_toggle(
+    state: AppState,
+    merchant_id: &str,
+    profile_id: &str,
+    connector_agnostic_mit_choice: admin_types::ConnectorAgnosticMitChoice,
+) -> RouterResponse<admin_types::ConnectorAgnosticMitChoice> {
+    let db = state.store.as_ref();
+
+    let business_profile = db
+        .find_business_profile_by_profile_id(profile_id)
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound {
+            id: profile_id.to_string(),
+        })?;
+
+    if business_profile.merchant_id != merchant_id {
+        Err(errors::ApiErrorResponse::AccessForbidden {
+            resource: profile_id.to_string(),
+        })?
+    }
+
+    if business_profile.is_connector_agnostic_mit_enabled
+        != Some(connector_agnostic_mit_choice.enabled)
+    {
+        let business_profile_update =
+            storage::business_profile::BusinessProfileUpdate::ConnectorAgnosticMitUpdate {
+                is_connector_agnostic_mit_enabled: Some(connector_agnostic_mit_choice.enabled),
+            };
+
+        db.update_business_profile_by_profile_id(business_profile, business_profile_update)
+            .await
+            .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound {
+                id: profile_id.to_owned(),
+            })?;
+    }
+
+    Ok(service_api::ApplicationResponse::Json(
+        connector_agnostic_mit_choice,
+    ))
+}
+
 pub(crate) fn validate_auth_and_metadata_type(
     connector_name: api_models::enums::Connector,
     val: &types::ConnectorAuthType,
diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs
index 63d9f840a00..d9cadf002c1 100644
--- a/crates/router/src/routes/admin.rs
+++ b/crates/router/src/routes/admin.rs
@@ -589,6 +589,32 @@ pub async fn business_profiles_list(
     )
     .await
 }
+
+#[instrument(skip_all, fields(flow = ?Flow::ToggleConnectorAgnosticMit))]
+pub async fn toggle_connector_agnostic_mit(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    path: web::Path<(String, String)>,
+    json_payload: web::Json<api_models::admin::ConnectorAgnosticMitChoice>,
+) -> HttpResponse {
+    let flow = Flow::ToggleConnectorAgnosticMit;
+    let (merchant_id, profile_id) = path.into_inner();
+
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        json_payload.into_inner(),
+        |state, _, req, _| connector_agnostic_mit_toggle(state, &merchant_id, &profile_id, req),
+        auth::auth_type(
+            &auth::ApiKeyAuth,
+            &auth::JWTAuth(Permission::RoutingWrite),
+            req.headers(),
+        ),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
 /// Merchant Account - KV Status
 ///
 /// Toggle KV mode for the Merchant Account
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 19a632bf895..c6b70ee5a9d 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1131,6 +1131,10 @@ impl BusinessProfile {
                     .service(
                         web::resource("/toggle_extended_card_info")
                             .route(web::post().to(toggle_extended_card_info)),
+                    )
+                    .service(
+                        web::resource("/toggle_connector_agnostic_mit")
+                            .route(web::post().to(toggle_connector_agnostic_mit)),
                     ),
             )
     }
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index faef85f3901..69dbb8c0cfc 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -168,7 +168,8 @@ impl From<Flow> for ApiIdentifier {
             | Flow::BusinessProfileRetrieve
             | Flow::BusinessProfileDelete
             | Flow::BusinessProfileList
-            | Flow::ToggleExtendedCardInfo => Self::Business,
+            | Flow::ToggleExtendedCardInfo
+            | Flow::ToggleConnectorAgnosticMit => Self::Business,
 
             Flow::PaymentLinkRetrieve
             | Flow::PaymentLinkInitiate
diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs
index 77fe8371817..793d042ea49 100644
--- a/crates/router/src/types/api/admin.rs
+++ b/crates/router/src/types/api/admin.rs
@@ -175,6 +175,7 @@ impl ForeignTryFrom<(domain::MerchantAccount, BusinessProfileCreate)>
                 .change_context(errors::ApiErrorResponse::InvalidDataValue {
                     field_name: "authentication_connector_details",
                 })?,
+            is_connector_agnostic_mit_enabled: None,
             is_extended_card_info_enabled: None,
             extended_card_info_config: None,
         })
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 26cb619d74c..bfb297922fb 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -404,6 +404,8 @@ pub enum Flow {
     RetrievePollStatus,
     /// Toggles the extended card info feature in profile level
     ToggleExtendedCardInfo,
+    /// Toggles the extended card info feature in profile level
+    ToggleConnectorAgnosticMit,
     /// Get the extended card info associated to a payment_id
     GetExtendedCardInfo,
 }
diff --git a/migrations/2024-04-24-111807_add-is-connector_agnostic_mit/down.sql b/migrations/2024-04-24-111807_add-is-connector_agnostic_mit/down.sql
new file mode 100644
index 00000000000..80672e85a26
--- /dev/null
+++ b/migrations/2024-04-24-111807_add-is-connector_agnostic_mit/down.sql
@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+
+ALTER TABLE business_profile DROP COLUMN IF EXISTS is_connector_agnostic_mit_enabled;
\ No newline at end of file
diff --git a/migrations/2024-04-24-111807_add-is-connector_agnostic_mit/up.sql b/migrations/2024-04-24-111807_add-is-connector_agnostic_mit/up.sql
new file mode 100644
index 00000000000..beee3325617
--- /dev/null
+++ b/migrations/2024-04-24-111807_add-is-connector_agnostic_mit/up.sql
@@ -0,0 +1,3 @@
+-- Your SQL goes here
+
+ALTER TABLE business_profile ADD COLUMN IF NOT EXISTS is_connector_agnostic_mit_enabled BOOLEAN DEFAULT FALSE;
\ 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 pr adds a api to enable usage of Network Transaction/Reference ID in recurring MIT payments. This will be a business profile level feature that a merchant specifies.

### 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) Create a business profile for a merchant
2) Use the below curl to enable `connector_agnostic_mit` feature for the business profile
```
curl --location 'http://localhost:8080/account/:merchant_id/business_profile/:profile_id/toggle_connector_agnostic_mit' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: api_key' \
--data '{
    "enabled": true
}'
```
<img width="839" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/67bcfaf9-ce19-435b-9846-8f74caea4310">

![image](https://github.com/juspay/hyperswitch/assets/83439957/97b5164f-ae42-4ba2-a67d-63a0ccb56fb7)


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