Bug: Accepting merchant public key and ttl for encrypting payload



Collect `public_key` and `raw_card_ttl` and store it in `business_profile` table. Use update endpoint to achieve this
diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs
index cfc9694dda3..d4d1b3d80b9 100644
--- a/crates/api_models/src/admin.rs
+++ b/crates/api_models/src/admin.rs
@@ -1042,6 +1042,9 @@ pub struct BusinessProfileUpdate {
 
     /// External 3DS authentication details
     pub authentication_connector_details: Option<AuthenticationConnectorDetails>,
+
+    /// Merchant's config to support extended card info feature
+    pub extended_card_info_config: Option<ExtendedCardInfoConfig>,
 }
 
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
@@ -1095,3 +1098,9 @@ pub struct ExtendedCardInfoChoice {
 }
 
 impl common_utils::events::ApiEventMetric for ExtendedCardInfoChoice {}
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct ExtendedCardInfoConfig {
+    pub public_key: Secret<String>,
+    pub ttl_in_secs: u16,
+}
diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs
index 7911aa403d6..90a9dcbb7d8 100644
--- a/crates/diesel_models/src/business_profile.rs
+++ b/crates/diesel_models/src/business_profile.rs
@@ -36,6 +36,7 @@ pub struct BusinessProfile {
     pub session_expiry: Option<i64>,
     pub authentication_connector_details: Option<serde_json::Value>,
     pub is_extended_card_info_enabled: Option<bool>,
+    pub extended_card_info_config: Option<pii::SecretSerdeValue>,
 }
 
 #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
@@ -63,6 +64,7 @@ pub struct BusinessProfileNew {
     pub session_expiry: Option<i64>,
     pub authentication_connector_details: Option<serde_json::Value>,
     pub is_extended_card_info_enabled: Option<bool>,
+    pub extended_card_info_config: Option<pii::SecretSerdeValue>,
 }
 
 #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
@@ -87,6 +89,7 @@ pub struct BusinessProfileUpdateInternal {
     pub session_expiry: Option<i64>,
     pub authentication_connector_details: Option<serde_json::Value>,
     pub is_extended_card_info_enabled: Option<bool>,
+    pub extended_card_info_config: Option<pii::SecretSerdeValue>,
 }
 
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -109,6 +112,7 @@ pub enum BusinessProfileUpdate {
         payment_link_config: Option<serde_json::Value>,
         session_expiry: Option<i64>,
         authentication_connector_details: Option<serde_json::Value>,
+        extended_card_info_config: Option<pii::SecretSerdeValue>,
     },
     ExtendedCardInfoUpdate {
         is_extended_card_info_enabled: Option<bool>,
@@ -136,6 +140,7 @@ impl From<BusinessProfileUpdate> for BusinessProfileUpdateInternal {
                 payment_link_config,
                 session_expiry,
                 authentication_connector_details,
+                extended_card_info_config,
             } => Self {
                 profile_name,
                 modified_at,
@@ -154,6 +159,7 @@ impl From<BusinessProfileUpdate> for BusinessProfileUpdateInternal {
                 payment_link_config,
                 session_expiry,
                 authentication_connector_details,
+                extended_card_info_config,
                 ..Default::default()
             },
             BusinessProfileUpdate::ExtendedCardInfoUpdate {
@@ -190,6 +196,7 @@ impl From<BusinessProfileNew> for BusinessProfile {
             session_expiry: new.session_expiry,
             authentication_connector_details: new.authentication_connector_details,
             is_extended_card_info_enabled: new.is_extended_card_info_enabled,
+            extended_card_info_config: new.extended_card_info_config,
         }
     }
 }
@@ -215,6 +222,7 @@ impl BusinessProfileUpdate {
             session_expiry,
             authentication_connector_details,
             is_extended_card_info_enabled,
+            extended_card_info_config,
         } = self.into();
         BusinessProfile {
             profile_name: profile_name.unwrap_or(source.profile_name),
@@ -237,6 +245,7 @@ impl BusinessProfileUpdate {
             session_expiry,
             authentication_connector_details,
             is_extended_card_info_enabled,
+            extended_card_info_config,
             ..source
         }
     }
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index c4277a5cc7a..e284e780d2e 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -194,6 +194,7 @@ diesel::table! {
         session_expiry -> Nullable<Int8>,
         authentication_connector_details -> Nullable<Jsonb>,
         is_extended_card_info_enabled -> Nullable<Bool>,
+        extended_card_info_config -> Nullable<Jsonb>,
     }
 }
 
diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs
index 3c6ab1f31d6..eab7ea6a7ed 100644
--- a/crates/router/src/core/admin.rs
+++ b/crates/router/src/core/admin.rs
@@ -439,6 +439,7 @@ pub async fn update_business_profile_cascade(
             payment_link_config: None,
             session_expiry: None,
             authentication_connector_details: None,
+            extended_card_info_config: None,
         };
 
         let update_futures = business_profiles.iter().map(|business_profile| async {
@@ -1648,6 +1649,19 @@ pub async fn update_business_profile(
         })
         .transpose()?;
 
+    let extended_card_info_config = request
+        .extended_card_info_config
+        .as_ref()
+        .map(|config| {
+            config
+                .encode_to_value()
+                .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                    field_name: "extended_card_info_config",
+                })
+        })
+        .transpose()?
+        .map(Secret::new);
+
     let business_profile_update = storage::business_profile::BusinessProfileUpdate::Update {
         profile_name: request.profile_name,
         modified_at: Some(date_time::now()),
@@ -1676,6 +1690,7 @@ pub async fn update_business_profile(
             .change_context(errors::ApiErrorResponse::InvalidDataValue {
                 field_name: "authentication_connector_details",
             })?,
+        extended_card_info_config,
     };
 
     let updated_business_profile = db
diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs
index 62925dcc2a5..fb86ba5a87b 100644
--- a/crates/router/src/core/routing/helpers.rs
+++ b/crates/router/src/core/routing/helpers.rs
@@ -263,6 +263,7 @@ pub async fn update_business_profile_active_algorithm_ref(
         payment_link_config: None,
         session_expiry: None,
         authentication_connector_details: None,
+        extended_card_info_config: None,
     };
     db.update_business_profile_by_profile_id(current_business_profile, business_profile_update)
         .await
diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs
index 06935a29fb3..77fe8371817 100644
--- a/crates/router/src/types/api/admin.rs
+++ b/crates/router/src/types/api/admin.rs
@@ -176,6 +176,7 @@ impl ForeignTryFrom<(domain::MerchantAccount, BusinessProfileCreate)>
                     field_name: "authentication_connector_details",
                 })?,
             is_extended_card_info_enabled: None,
+            extended_card_info_config: None,
         })
     }
 }
diff --git a/migrations/2024-04-24-075735_add-merchant-pkey-ttl-to-business-profile/down.sql b/migrations/2024-04-24-075735_add-merchant-pkey-ttl-to-business-profile/down.sql
new file mode 100644
index 00000000000..63a07052cbe
--- /dev/null
+++ b/migrations/2024-04-24-075735_add-merchant-pkey-ttl-to-business-profile/down.sql
@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+
+ALTER TABLE business_profile DROP COLUMN IF EXISTS extended_card_info_config;
\ No newline at end of file
diff --git a/migrations/2024-04-24-075735_add-merchant-pkey-ttl-to-business-profile/up.sql b/migrations/2024-04-24-075735_add-merchant-pkey-ttl-to-business-profile/up.sql
new file mode 100644
index 00000000000..f1b9ca58f84
--- /dev/null
+++ b/migrations/2024-04-24-075735_add-merchant-pkey-ttl-to-business-profile/up.sql
@@ -0,0 +1,3 @@
+-- Your SQL goes here
+
+ALTER TABLE business_profile ADD COLUMN IF NOT EXISTS extended_card_info_config JSONB DEFAULT NULL;
\ 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 support for merchant to pass his public key as well as ttl using update endpoint of business profile. The config passed is stored in business profile table as JSONB.

### Additional Changes

- [ ] This PR modifies the API contract
- [x] 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 (Default one which gets created during merchant account creation works too)
2. Use update endpoint of business profile to pass config.
```
curl --location 'http://localhost:8080/account/:merchant_id/business_profile/:profile_id' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data '{
    "extended_card_info_config": {
        "public_key": "pub_key_1",
        "ttl_in_secs": 60
    }
}'
```
![image](https://github.com/juspay/hyperswitch/assets/70657455/67787f0e-724c-4bed-ae37-a2636093e481)

3. For verifying, u could check the db if the config is stored in `extended_card_info_config` column in `business_profile` table using below query
```
SELECT extended_card_info_config FROM business_profile WHERE profile_id = 'profile_id';
```

## 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
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
