Bug: add an api to migrate the payment method



This is the API that will be used to migrate card details and associated data stored in another service to Hyperswitch.

How does this API differ from the existing payment method creation API?

1. It takes masked card details as input with valid first six and last four card numbers.
2. It also accepts `connector_mandate_details` and `network_transaction_id` if present.

If the masked card number is provided as input, a payment method entry is created in Hyperswitch, and no data is stored in the locker. If a valid card number is present, the card details are stored in the locker, the same as in the save card flow.
diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs
index cb268262fbc..5b360e39956 100644
--- a/crates/api_models/src/admin.rs
+++ b/crates/api_models/src/admin.rs
@@ -1229,6 +1229,8 @@ pub struct ConnectorAgnosticMitChoice {
 
 impl common_utils::events::ApiEventMetric for ConnectorAgnosticMitChoice {}
 
+impl common_utils::events::ApiEventMetric for payment_methods::PaymentMethodMigrate {}
+
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
 pub struct ExtendedCardInfoConfig {
     /// Merchant public key
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index c8826c02089..7b33e3dfeab 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -75,6 +75,128 @@ pub struct PaymentMethodCreate {
     /// Payment method data to be passed in case of client
     /// based flow
     pub payment_method_data: Option<PaymentMethodCreateData>,
+
+    /// The billing details of the payment method
+    #[schema(value_type = Option<Address>)]
+    pub billing: Option<payments::Address>,
+
+    #[serde(skip_deserializing)]
+    /// The connector mandate details of the payment method, this is added only for cards migration
+    /// api and is skipped during deserialization of the payment method create request as this
+    /// it should not be passed in the request
+    pub connector_mandate_details: Option<PaymentsMandateReference>,
+
+    #[serde(skip_deserializing)]
+    /// The transaction id of a CIT (customer initiated transaction) associated with the payment method,
+    /// this is added only for cards migration api and is skipped during deserialization of the
+    /// payment method create request as it should not be passed in the request
+    pub network_transaction_id: Option<String>,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
+/// This struct is only used by and internal api to migrate payment method
+pub struct PaymentMethodMigrate {
+    /// Merchant id
+    pub merchant_id: String,
+
+    /// The type of payment method use for the payment.
+    pub payment_method: Option<api_enums::PaymentMethod>,
+
+    /// This is a sub-category of payment method.
+    pub payment_method_type: Option<api_enums::PaymentMethodType>,
+
+    /// The name of the bank/ provider issuing the payment method to the end user
+    pub payment_method_issuer: Option<String>,
+
+    /// A standard code representing the issuer of payment method
+    pub payment_method_issuer_code: Option<api_enums::PaymentMethodIssuerCode>,
+
+    /// Card Details
+    pub card: Option<MigrateCardDetail>,
+
+    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
+    pub metadata: Option<pii::SecretSerdeValue>,
+
+    /// The unique identifier of the customer.
+    pub customer_id: Option<id_type::CustomerId>,
+
+    /// The card network
+    pub card_network: Option<String>,
+
+    /// Payment method details from locker
+    #[cfg(feature = "payouts")]
+    pub bank_transfer: Option<payouts::Bank>,
+
+    /// Payment method details from locker
+    #[cfg(feature = "payouts")]
+    pub wallet: Option<payouts::Wallet>,
+
+    /// Payment method data to be passed in case of client
+    /// based flow
+    pub payment_method_data: Option<PaymentMethodCreateData>,
+
+    /// The billing details of the payment method
+    pub billing: Option<payments::Address>,
+
+    /// The connector mandate details of the payment method
+    pub connector_mandate_details: Option<PaymentsMandateReference>,
+
+    // The CIT (customer initiated transaction) transaction id associated with the payment method
+    pub network_transaction_id: Option<String>,
+}
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct PaymentsMandateReference(pub HashMap<String, PaymentsMandateReferenceRecord>);
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct PaymentsMandateReferenceRecord {
+    pub connector_mandate_id: String,
+    pub payment_method_type: Option<common_enums::PaymentMethodType>,
+    pub original_payment_authorized_amount: Option<i64>,
+    pub original_payment_authorized_currency: Option<common_enums::Currency>,
+}
+
+impl PaymentMethodCreate {
+    pub fn get_payment_method_create_from_payment_method_migrate(
+        card_number: CardNumber,
+        payment_method_migrate: &PaymentMethodMigrate,
+    ) -> Self {
+        let card_details =
+            payment_method_migrate
+                .card
+                .as_ref()
+                .map(|payment_method_migrate_card| CardDetail {
+                    card_number,
+                    card_exp_month: payment_method_migrate_card.card_exp_month.clone(),
+                    card_exp_year: payment_method_migrate_card.card_exp_year.clone(),
+                    card_holder_name: payment_method_migrate_card.card_holder_name.clone(),
+                    nick_name: payment_method_migrate_card.nick_name.clone(),
+                    card_issuing_country: payment_method_migrate_card.card_issuing_country.clone(),
+                    card_network: payment_method_migrate_card.card_network.clone(),
+                    card_issuer: payment_method_migrate_card.card_issuer.clone(),
+                    card_type: payment_method_migrate_card.card_type.clone(),
+                });
+
+        Self {
+            customer_id: payment_method_migrate.customer_id.clone(),
+            payment_method: payment_method_migrate.payment_method,
+            payment_method_type: payment_method_migrate.payment_method_type,
+            payment_method_issuer: payment_method_migrate.payment_method_issuer.clone(),
+            payment_method_issuer_code: payment_method_migrate.payment_method_issuer_code,
+            metadata: payment_method_migrate.metadata.clone(),
+            payment_method_data: payment_method_migrate.payment_method_data.clone(),
+            connector_mandate_details: payment_method_migrate.connector_mandate_details.clone(),
+            client_secret: None,
+            billing: payment_method_migrate.billing.clone(),
+            card: card_details,
+            card_network: payment_method_migrate.card_network.clone(),
+            #[cfg(feature = "payouts")]
+            bank_transfer: payment_method_migrate.bank_transfer.clone(),
+            #[cfg(feature = "payouts")]
+            wallet: payment_method_migrate.wallet.clone(),
+            network_transaction_id: payment_method_migrate.network_transaction_id.clone(),
+        }
+    }
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
@@ -139,6 +261,43 @@ pub struct CardDetail {
     pub card_type: Option<String>,
 }
 
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
+#[serde(deny_unknown_fields)]
+pub struct MigrateCardDetail {
+    /// Card Number
+    #[schema(value_type = String,example = "4111111145551142")]
+    pub card_number: masking::Secret<String>,
+
+    /// Card Expiry Month
+    #[schema(value_type = String,example = "10")]
+    pub card_exp_month: masking::Secret<String>,
+
+    /// Card Expiry Year
+    #[schema(value_type = String,example = "25")]
+    pub card_exp_year: masking::Secret<String>,
+
+    /// Card Holder Name
+    #[schema(value_type = String,example = "John Doe")]
+    pub card_holder_name: Option<masking::Secret<String>>,
+
+    /// Card Holder's Nick Name
+    #[schema(value_type = Option<String>,example = "John Doe")]
+    pub nick_name: Option<masking::Secret<String>>,
+
+    /// Card Issuing Country
+    pub card_issuing_country: Option<String>,
+
+    /// Card's Network
+    #[schema(value_type = Option<CardNetwork>)]
+    pub card_network: Option<api_enums::CardNetwork>,
+
+    /// Issuer Bank for Card
+    pub card_issuer: Option<String>,
+
+    /// Card Type
+    pub card_type: Option<String>,
+}
+
 #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
 #[serde(deny_unknown_fields)]
 pub struct CardDetailUpdate {
diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs
index d968e62bea8..24ba06bcf36 100644
--- a/crates/router/src/core/locker_migration.rs
+++ b/crates/router/src/core/locker_migration.rs
@@ -130,6 +130,9 @@ pub async fn call_to_locker(
             card_network: card.card_brand,
             client_secret: None,
             payment_method_data: None,
+            billing: None,
+            connector_mandate_details: None,
+            network_transaction_id: None,
         };
 
         let add_card_result = cards::add_card_hs(
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 495d85d5b36..ef1fa51442b 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -95,6 +95,7 @@ pub async fn create_payment_method(
     network_transaction_id: Option<String>,
     storage_scheme: MerchantStorageScheme,
     payment_method_billing_address: Option<Encryption>,
+    card_scheme: Option<String>,
 ) -> errors::CustomResult<storage::PaymentMethod, errors::ApiErrorResponse> {
     let customer = db
         .find_customer_by_customer_id_merchant_id(
@@ -123,7 +124,7 @@ pub async fn create_payment_method(
                 payment_method: req.payment_method,
                 payment_method_type: req.payment_method_type,
                 payment_method_issuer: req.payment_method_issuer.clone(),
-                scheme: req.card_network.clone(),
+                scheme: req.card_network.clone().or(card_scheme),
                 metadata: pm_metadata.map(Secret::new),
                 payment_method_data,
                 connector_mandate_details,
@@ -244,7 +245,7 @@ pub async fn get_or_insert_payment_method(
                 insert_payment_method(
                     db,
                     resp,
-                    req,
+                    &req,
                     key_store,
                     &merchant_account.merchant_id,
                     customer_id,
@@ -252,7 +253,7 @@ pub async fn get_or_insert_payment_method(
                     None,
                     locker_id,
                     None,
-                    None,
+                    req.network_transaction_id.clone(),
                     merchant_account.storage_scheme,
                     None,
                 )
@@ -266,6 +267,314 @@ pub async fn get_or_insert_payment_method(
     }
 }
 
+pub async fn migrate_payment_method(
+    state: routes::SessionState,
+    req: api::PaymentMethodMigrate,
+) -> errors::RouterResponse<api::PaymentMethodResponse> {
+    let merchant_id = &req.merchant_id;
+    let key_store = state
+        .store
+        .get_merchant_key_store_by_merchant_id(
+            merchant_id,
+            &state.store.get_master_key().to_vec().into(),
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
+
+    let merchant_account = state
+        .store
+        .find_merchant_account_by_merchant_id(merchant_id, &key_store)
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
+
+    let card_details = req.card.as_ref().get_required_value("card")?;
+
+    let card_number_validation_result =
+        cards::CardNumber::from_str(card_details.card_number.peek());
+
+    if let Some(connector_mandate_details) = &req.connector_mandate_details {
+        helpers::validate_merchant_connector_ids_in_connector_mandate_details(
+            &*state.store,
+            &key_store,
+            connector_mandate_details,
+            merchant_id,
+        )
+        .await?;
+    };
+
+    match card_number_validation_result {
+        Ok(card_number) => {
+            let payment_method_create_request =
+                api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate(
+                    card_number,
+                    &req,
+                );
+            get_client_secret_or_add_payment_method(
+                state,
+                payment_method_create_request,
+                &merchant_account,
+                &key_store,
+            )
+            .await
+        }
+        Err(card_validation_error) => {
+            logger::debug!("Card number to be migrated is invalid, skip saving in locker {card_validation_error}");
+            skip_locker_call_and_migrate_payment_method(
+                state,
+                &req,
+                merchant_id.into(),
+                &key_store,
+                &merchant_account,
+            )
+            .await
+        }
+    }
+}
+
+pub async fn skip_locker_call_and_migrate_payment_method(
+    state: routes::SessionState,
+    req: &api::PaymentMethodMigrate,
+    merchant_id: String,
+    key_store: &domain::MerchantKeyStore,
+    merchant_account: &domain::MerchantAccount,
+) -> errors::RouterResponse<api::PaymentMethodResponse> {
+    let db = &*state.store;
+    let customer_id = req.customer_id.clone().get_required_value("customer_id")?;
+
+    // In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details.
+    let connector_mandate_details_req = req
+        .connector_mandate_details
+        .clone()
+        .get_required_value("connector mandate details")?;
+
+    let connector_mandate_details = serde_json::to_value(&connector_mandate_details_req)
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed to parse connector mandate details")?;
+
+    let payment_method_billing_address = create_encrypted_data(key_store, req.billing.clone())
+        .await
+        .map(|details| details.into());
+
+    let customer = db
+        .find_customer_by_customer_id_merchant_id(
+            &customer_id,
+            &merchant_id,
+            key_store,
+            merchant_account.storage_scheme,
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;
+
+    let card = if let Some(card_details) = &req.card {
+        helpers::validate_card_expiry(&card_details.card_exp_month, &card_details.card_exp_year)?;
+        let card_number = card_details.card_number.clone();
+
+        let (card_isin, last4_digits) = get_card_bin_and_last4_digits_for_masked_card(
+            card_number.peek(),
+        )
+        .change_context(errors::ApiErrorResponse::InvalidRequestData {
+            message: "Invalid card number".to_string(),
+        })?;
+
+        if card_details.card_issuer.is_some()
+            && card_details.card_network.is_some()
+            && card_details.card_type.is_some()
+            && card_details.card_issuing_country.is_some()
+        {
+            Some(api::CardDetailFromLocker {
+                scheme: card_details
+                    .card_network
+                    .clone()
+                    .or(card_details.card_network.clone())
+                    .map(|card_network| card_network.to_string()),
+                last4_digits: Some(last4_digits.clone()),
+                issuer_country: card_details
+                    .card_issuing_country
+                    .clone()
+                    .or(card_details.card_issuing_country.clone()),
+                card_number: None,
+                expiry_month: Some(card_details.card_exp_month.clone()),
+                expiry_year: Some(card_details.card_exp_year.clone()),
+                card_token: None,
+                card_fingerprint: None,
+                card_holder_name: card_details.card_holder_name.clone(),
+                nick_name: card_details.nick_name.clone(),
+                card_isin: Some(card_isin.clone()),
+                card_issuer: card_details
+                    .card_issuer
+                    .clone()
+                    .or(card_details.card_issuer.clone()),
+                card_network: card_details
+                    .card_network
+                    .clone()
+                    .or(card_details.card_network.clone()),
+                card_type: card_details
+                    .card_type
+                    .clone()
+                    .or(card_details.card_type.clone()),
+                saved_to_locker: false,
+            })
+        } else {
+            Some(
+                db.get_card_info(&card_isin)
+                    .await
+                    .map_err(|error| services::logger::error!(card_info_error=?error))
+                    .ok()
+                    .flatten()
+                    .map(|card_info| {
+                        logger::debug!("Fetching bin details");
+                        api::CardDetailFromLocker {
+                            scheme: card_details
+                                .card_network
+                                .clone()
+                                .or(card_info.card_network.clone())
+                                .map(|card_network| card_network.to_string()),
+                            last4_digits: Some(last4_digits.clone()),
+                            issuer_country: card_details
+                                .card_issuing_country
+                                .clone()
+                                .or(card_info.card_issuing_country),
+                            card_number: None,
+                            expiry_month: Some(card_details.card_exp_month.clone()),
+                            expiry_year: Some(card_details.card_exp_year.clone()),
+                            card_token: None,
+                            card_fingerprint: None,
+                            card_holder_name: card_details.card_holder_name.clone(),
+                            nick_name: card_details.nick_name.clone(),
+                            card_isin: Some(card_isin.clone()),
+                            card_issuer: card_details.card_issuer.clone().or(card_info.card_issuer),
+                            card_network: card_details
+                                .card_network
+                                .clone()
+                                .or(card_info.card_network),
+                            card_type: card_details.card_type.clone().or(card_info.card_type),
+                            saved_to_locker: false,
+                        }
+                    })
+                    .unwrap_or_else(|| {
+                        logger::debug!("Failed to fetch bin details");
+                        api::CardDetailFromLocker {
+                            scheme: card_details
+                                .card_network
+                                .clone()
+                                .map(|card_network| card_network.to_string()),
+                            last4_digits: Some(last4_digits.clone()),
+                            issuer_country: card_details.card_issuing_country.clone(),
+                            card_number: None,
+                            expiry_month: Some(card_details.card_exp_month.clone()),
+                            expiry_year: Some(card_details.card_exp_year.clone()),
+                            card_token: None,
+                            card_fingerprint: None,
+                            card_holder_name: card_details.card_holder_name.clone(),
+                            nick_name: card_details.nick_name.clone(),
+                            card_isin: Some(card_isin.clone()),
+                            card_issuer: card_details.card_issuer.clone(),
+                            card_network: card_details.card_network.clone(),
+                            card_type: card_details.card_type.clone(),
+                            saved_to_locker: false,
+                        }
+                    }),
+            )
+        }
+    } else {
+        None
+    };
+
+    let payment_method_card_details = card
+        .as_ref()
+        .map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())));
+
+    let payment_method_data_encrypted =
+        create_encrypted_data(key_store, payment_method_card_details)
+            .await
+            .map(|details| details.into());
+
+    let payment_method_metadata: Option<serde_json::Value> =
+        req.metadata.as_ref().map(|data| data.peek()).cloned();
+
+    let payment_method_id = generate_id(consts::ID_LENGTH, "pm");
+
+    let current_time = common_utils::date_time::now();
+
+    let response = db
+        .insert_payment_method(
+            storage::PaymentMethodNew {
+                customer_id: customer_id.to_owned(),
+                merchant_id: merchant_id.to_string(),
+                payment_method_id: payment_method_id.to_string(),
+                locker_id: None,
+                payment_method: req.payment_method,
+                payment_method_type: req.payment_method_type,
+                payment_method_issuer: req.payment_method_issuer.clone(),
+                scheme: req.card_network.clone(),
+                metadata: payment_method_metadata.map(Secret::new),
+                payment_method_data: payment_method_data_encrypted,
+                connector_mandate_details: Some(connector_mandate_details),
+                customer_acceptance: None,
+                client_secret: None,
+                status: enums::PaymentMethodStatus::Active,
+                network_transaction_id: None,
+                payment_method_issuer_code: None,
+                accepted_currency: None,
+                token: None,
+                cardholder_name: None,
+                issuer_name: None,
+                issuer_country: None,
+                payer_country: None,
+                is_stored: None,
+                swift_code: None,
+                direct_debit_token: None,
+                created_at: current_time,
+                last_modified: current_time,
+                last_used_at: current_time,
+                payment_method_billing_address,
+                updated_by: None,
+            },
+            merchant_account.storage_scheme,
+        )
+        .await
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed to add payment method in db")?;
+
+    logger::debug!("Payment method inserted in db");
+
+    if customer.default_payment_method_id.is_none() && req.payment_method.is_some() {
+        let _ = set_default_payment_method(
+            &*state.store,
+            merchant_id.to_string(),
+            key_store.clone(),
+            &customer_id,
+            payment_method_id.to_owned(),
+            merchant_account.storage_scheme,
+        )
+        .await
+        .map_err(|err| logger::error!(error=?err,"Failed to set the payment method as default"));
+    }
+    Ok(services::api::ApplicationResponse::Json(
+        api::PaymentMethodResponse::foreign_from((card, response)),
+    ))
+}
+
+pub fn get_card_bin_and_last4_digits_for_masked_card(
+    masked_card_number: &str,
+) -> Result<(String, String), cards::CardNumberValidationErr> {
+    let last4_digits = masked_card_number
+        .chars()
+        .rev()
+        .take(4)
+        .collect::<String>()
+        .chars()
+        .rev()
+        .collect::<String>();
+
+    let card_isin = masked_card_number.chars().take(6).collect::<String>();
+
+    cards::validate::validate_card_number_chars(&card_isin)
+        .and_then(|_| cards::validate::validate_card_number_chars(&last4_digits))?;
+
+    Ok((card_isin, last4_digits))
+}
+
 #[instrument(skip_all)]
 pub async fn get_client_secret_or_add_payment_method(
     state: routes::SessionState,
@@ -282,6 +591,17 @@ pub async fn get_client_secret_or_add_payment_method(
     #[cfg(feature = "payouts")]
     let condition = req.card.is_some() || req.bank_transfer.is_some() || req.wallet.is_some();
 
+    let payment_method_billing_address = create_encrypted_data(key_store, req.billing.clone())
+        .await
+        .map(|details| details.into());
+
+    let connector_mandate_details = req
+        .connector_mandate_details
+        .clone()
+        .map(serde_json::to_value)
+        .transpose()
+        .change_context(errors::ApiErrorResponse::InternalServerError)?;
+
     if condition {
         Box::pin(add_payment_method(state, req, merchant_account, key_store)).await
     } else {
@@ -298,10 +618,11 @@ pub async fn get_client_secret_or_add_payment_method(
             None,
             None,
             key_store,
-            None,
+            connector_mandate_details,
             Some(enums::PaymentMethodStatus::AwaitingData),
             None,
             merchant_account.storage_scheme,
+            payment_method_billing_address,
             None,
         )
         .await?;
@@ -322,7 +643,7 @@ pub async fn get_client_secret_or_add_payment_method(
         }
 
         Ok(services::api::ApplicationResponse::Json(
-            api::PaymentMethodResponse::foreign_from(res),
+            api::PaymentMethodResponse::foreign_from((None, res)),
         ))
     }
 }
@@ -544,6 +865,16 @@ pub async fn add_payment_method(
     let merchant_id = &merchant_account.merchant_id;
     let customer_id = req.customer_id.clone().get_required_value("customer_id")?;
     let payment_method = req.payment_method.get_required_value("payment_method")?;
+    let payment_method_billing_address = create_encrypted_data(key_store, req.billing.clone())
+        .await
+        .map(|details| details.into());
+
+    let connector_mandate_details = req
+        .connector_mandate_details
+        .clone()
+        .map(serde_json::to_value)
+        .transpose()
+        .change_context(errors::ApiErrorResponse::InternalServerError)?;
 
     let response = match payment_method {
         #[cfg(feature = "payouts")]
@@ -567,11 +898,20 @@ pub async fn add_payment_method(
         },
         api_enums::PaymentMethod::Card => match req.card.clone() {
             Some(card) => {
-                helpers::validate_card_expiry(&card.card_exp_month, &card.card_exp_year)?;
+                let mut card_details = card;
+                card_details = helpers::populate_bin_details_for_payment_method_create(
+                    card_details.clone(),
+                    db,
+                )
+                .await;
+                helpers::validate_card_expiry(
+                    &card_details.card_exp_month,
+                    &card_details.card_exp_year,
+                )?;
                 Box::pin(add_card_to_locker(
                     &state,
                     req.clone(),
-                    &card,
+                    &card_details,
                     &customer_id,
                     merchant_account,
                     None,
@@ -731,17 +1071,17 @@ pub async fn add_payment_method(
             let pm = insert_payment_method(
                 db,
                 &resp,
-                req,
+                &req,
                 key_store,
                 merchant_id,
                 &customer_id,
                 pm_metadata.cloned(),
                 None,
                 locker_id,
-                None,
-                None,
+                connector_mandate_details,
+                req.network_transaction_id.clone(),
                 merchant_account.storage_scheme,
-                None,
+                payment_method_billing_address,
             )
             .await?;
 
@@ -756,7 +1096,7 @@ pub async fn add_payment_method(
 pub async fn insert_payment_method(
     db: &dyn db::StorageInterface,
     resp: &api::PaymentMethodResponse,
-    req: api::PaymentMethodCreate,
+    req: &api::PaymentMethodCreate,
     key_store: &domain::MerchantKeyStore,
     merchant_id: &str,
     customer_id: &id_type::CustomerId,
@@ -770,7 +1110,7 @@ pub async fn insert_payment_method(
 ) -> errors::RouterResult<diesel_models::PaymentMethod> {
     let pm_card_details = resp
         .card
-        .as_ref()
+        .clone()
         .map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())));
     let pm_data_encrypted = create_encrypted_data(key_store, pm_card_details)
         .await
@@ -778,7 +1118,7 @@ pub async fn insert_payment_method(
 
     create_payment_method(
         db,
-        &req,
+        req,
         customer_id,
         &resp.payment_method_id,
         locker_id,
@@ -792,6 +1132,10 @@ pub async fn insert_payment_method(
         network_transaction_id,
         storage_scheme,
         payment_method_billing_address,
+        resp.card.clone().and_then(|card| {
+            card.card_network
+                .map(|card_network| card_network.to_string())
+        }),
     )
     .await
 }
@@ -904,6 +1248,9 @@ pub async fn update_customer_payment_method(
                 client_secret: pm.client_secret.clone(),
                 payment_method_data: None,
                 card_network: None,
+                billing: None,
+                connector_mandate_details: None,
+                network_transaction_id: None,
             };
             new_pm.validate()?;
 
@@ -1135,7 +1482,7 @@ pub async fn add_card_to_locker(
     errors::VaultError,
 > {
     metrics::STORED_TO_LOCKER.add(&metrics::CONTEXT, 1, &[]);
-    let add_card_to_hs_resp = common_utils::metrics::utils::record_operation_time(
+    let add_card_to_hs_resp = Box::pin(common_utils::metrics::utils::record_operation_time(
         async {
             add_card_hs(
                 state,
@@ -1162,7 +1509,7 @@ pub async fn add_card_to_locker(
         &metrics::CARD_ADD_TIME,
         &metrics::CONTEXT,
         &[router_env::opentelemetry::KeyValue::new("locker", "rust")],
-    )
+    ))
     .await?;
 
     logger::debug!("card added to hyperswitch-card-vault");
@@ -3677,12 +4024,8 @@ pub async fn get_card_details_with_locker_fallback(
             });
 
     Ok(if let Some(mut crd) = card_decrypted {
-        if crd.saved_to_locker {
-            crd.scheme.clone_from(&pm.scheme);
-            Some(crd)
-        } else {
-            None
-        }
+        crd.scheme.clone_from(&pm.scheme);
+        Some(crd)
     } else {
         logger::debug!(
             "Getting card details from locker as it is not found in payment methods table"
diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs
index 887c02dd023..6bfadabdb35 100644
--- a/crates/router/src/core/payment_methods/transformers.rs
+++ b/crates/router/src/core/payment_methods/transformers.rs
@@ -348,7 +348,10 @@ pub fn mk_add_card_response_hs(
     let card_isin = card_number.get_card_isin();
 
     let card = api::CardDetailFromLocker {
-        scheme: None,
+        scheme: card
+            .card_network
+            .clone()
+            .map(|card_network| card_network.to_string()),
         last4_digits: Some(last4_digits),
         issuer_country: card.card_issuing_country,
         card_number: Some(card.card_number.clone()),
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index c19480f1d56..745a69a1f56 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -1385,6 +1385,9 @@ pub(crate) async fn get_payment_method_create_request(
                             .map(|card_network| card_network.to_string()),
                         client_secret: None,
                         payment_method_data: None,
+                        billing: None,
+                        connector_mandate_details: None,
+                        network_transaction_id: None,
                     };
                     Ok(payment_method_request)
                 }
@@ -1404,6 +1407,9 @@ pub(crate) async fn get_payment_method_create_request(
                         card_network: None,
                         client_secret: None,
                         payment_method_data: None,
+                        billing: None,
+                        connector_mandate_details: None,
+                        network_transaction_id: None,
                     };
 
                     Ok(payment_method_request)
@@ -4074,6 +4080,63 @@ pub async fn get_additional_payment_data(
     }
 }
 
+pub async fn populate_bin_details_for_payment_method_create(
+    card_details: api_models::payment_methods::CardDetail,
+    db: &dyn StorageInterface,
+) -> api_models::payment_methods::CardDetail {
+    let card_isin: Option<_> = Some(card_details.card_number.get_card_isin());
+    if card_details.card_issuer.is_some()
+        && card_details.card_network.is_some()
+        && card_details.card_type.is_some()
+        && card_details.card_issuing_country.is_some()
+    {
+        api::CardDetail {
+            card_issuer: card_details.card_issuer.to_owned(),
+            card_network: card_details.card_network.clone(),
+            card_type: card_details.card_type.to_owned(),
+            card_issuing_country: card_details.card_issuing_country.to_owned(),
+            card_exp_month: card_details.card_exp_month.clone(),
+            card_exp_year: card_details.card_exp_year.clone(),
+            card_holder_name: card_details.card_holder_name.clone(),
+            card_number: card_details.card_number.clone(),
+            nick_name: card_details.nick_name.clone(),
+        }
+    } else {
+        let card_info = card_isin
+            .clone()
+            .async_and_then(|card_isin| async move {
+                db.get_card_info(&card_isin)
+                    .await
+                    .map_err(|error| services::logger::error!(card_info_error=?error))
+                    .ok()
+            })
+            .await
+            .flatten()
+            .map(|card_info| api::CardDetail {
+                card_issuer: card_info.card_issuer,
+                card_network: card_info.card_network.clone(),
+                card_type: card_info.card_type,
+                card_issuing_country: card_info.card_issuing_country,
+                card_exp_month: card_details.card_exp_month.clone(),
+                card_exp_year: card_details.card_exp_year.clone(),
+                card_holder_name: card_details.card_holder_name.clone(),
+                card_number: card_details.card_number.clone(),
+                nick_name: card_details.nick_name.clone(),
+            });
+        card_info.unwrap_or_else(|| api::CardDetail {
+            card_issuer: None,
+            card_network: None,
+            card_type: None,
+            card_issuing_country: None,
+            card_exp_month: card_details.card_exp_month.clone(),
+            card_exp_year: card_details.card_exp_year.clone(),
+            card_holder_name: card_details.card_holder_name.clone(),
+            card_number: card_details.card_number.clone(),
+            nick_name: card_details.nick_name.clone(),
+        })
+    }
+}
+
 pub fn validate_customer_access(
     payment_intent: &PaymentIntent,
     auth_flow: services::AuthFlow,
@@ -5057,3 +5120,36 @@ pub async fn override_setup_future_usage_to_on_session<F: Clone>(
     };
     Ok(())
 }
+
+pub async fn validate_merchant_connector_ids_in_connector_mandate_details(
+    db: &dyn StorageInterface,
+    key_store: &domain::MerchantKeyStore,
+    connector_mandate_details: &api_models::payment_methods::PaymentsMandateReference,
+    merchant_id: &str,
+) -> CustomResult<(), errors::ApiErrorResponse> {
+    let merchant_connector_account_list = db
+        .find_merchant_connector_account_by_merchant_id_and_disabled_list(
+            merchant_id,
+            true,
+            key_store,
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;
+
+    let merchant_connector_ids: Vec<String> = merchant_connector_account_list
+        .iter()
+        .map(|merchant_connector_account| merchant_connector_account.merchant_connector_id.clone())
+        .collect();
+
+    for merchant_connector_id in connector_mandate_details.0.keys() {
+        if !merchant_connector_ids.contains(merchant_connector_id) {
+            Err(errors::ApiErrorResponse::InvalidDataValue {
+                field_name: "merchant_connector_id",
+            })
+            .attach_printable_lazy(|| {
+                format!("{merchant_connector_id} invalid merchant connector id in connector_mandate_details")
+            })?
+        }
+    }
+    Ok(())
+}
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index f63278cddc4..3fb67abd57e 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -318,6 +318,10 @@ where
                                             network_transaction_id,
                                             merchant_account.storage_scheme,
                                             encrypted_payment_method_billing_address,
+                                            resp.card.and_then(|card| {
+                                                card.card_network
+                                                    .map(|card_network| card_network.to_string())
+                                            }),
                                         )
                                         .await
                                     } else {
@@ -397,7 +401,7 @@ where
                                             payment_methods::cards::insert_payment_method(
                                                 db,
                                                 &resp,
-                                                payment_method_create_request.clone(),
+                                                &payment_method_create_request.clone(),
                                                 key_store,
                                                 &merchant_account.merchant_id,
                                                 &customer_id,
@@ -602,6 +606,10 @@ where
                                 network_transaction_id,
                                 merchant_account.storage_scheme,
                                 encrypted_payment_method_billing_address,
+                                resp.card.and_then(|card| {
+                                    card.card_network
+                                        .map(|card_network| card_network.to_string())
+                                }),
                             )
                             .await?;
                         };
diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs
index ba00370e881..f68a5271fd7 100644
--- a/crates/router/src/core/payouts/helpers.rs
+++ b/crates/router/src/core/payouts/helpers.rs
@@ -391,6 +391,9 @@ pub async fn save_payout_data_to_locker(
                 card_network: None,
                 client_secret: None,
                 payment_method_data: None,
+                billing: None,
+                connector_mandate_details: None,
+                network_transaction_id: None,
             };
 
             let pm_data = card_isin
@@ -472,6 +475,9 @@ pub async fn save_payout_data_to_locker(
                     card_network: None,
                     client_secret: None,
                     payment_method_data: None,
+                    billing: None,
+                    connector_mandate_details: None,
+                    network_transaction_id: None,
                 },
             )
         };
@@ -495,6 +501,7 @@ pub async fn save_payout_data_to_locker(
             None,
             merchant_account.storage_scheme,
             None,
+            None,
         )
         .await?;
     }
diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs
index 508b9e8ef8b..d3393b5b1f7 100644
--- a/crates/router/src/core/pm_auth.rs
+++ b/crates/router/src/core/pm_auth.rs
@@ -478,7 +478,6 @@ async fn store_bank_details_in_payment_methods(
                 last_used_at: now,
                 connector_mandate_details: None,
                 customer_acceptance: None,
-
                 network_transaction_id: None,
                 client_secret: None,
                 payment_method_billing_address: None,
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 4fad6f863e9..ba94879cd68 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -931,6 +931,9 @@ impl PaymentMethods {
                         .route(web::post().to(create_payment_method_api))
                         .route(web::get().to(list_payment_method_api)), // TODO : added for sdk compatibility for now, need to deprecate this later
                 )
+                .service(
+                    web::resource("/migrate").route(web::post().to(migrate_payment_method_api)),
+                )
                 .service(
                     web::resource("/collect").route(web::post().to(initiate_pm_collect_link_flow)),
                 )
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index fb5be71b521..56e8751a979 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -93,6 +93,7 @@ impl From<Flow> for ApiIdentifier {
             Flow::MandatesRetrieve | Flow::MandatesRevoke | Flow::MandatesList => Self::Mandates,
 
             Flow::PaymentMethodsCreate
+            | Flow::PaymentMethodsMigrate
             | Flow::PaymentMethodsList
             | Flow::CustomerPaymentMethodsList
             | Flow::PaymentMethodsRetrieve
diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs
index bc748572150..8ff6e48237c 100644
--- a/crates/router/src/routes/payment_methods.rs
+++ b/crates/router/src/routes/payment_methods.rs
@@ -46,6 +46,26 @@ pub async fn create_payment_method_api(
     .await
 }
 
+#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsMigrate))]
+pub async fn migrate_payment_method_api(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    json_payload: web::Json<payment_methods::PaymentMethodMigrate>,
+) -> HttpResponse {
+    let flow = Flow::PaymentMethodsMigrate;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        json_payload.into_inner(),
+        |state, _, req, _| async move { Box::pin(cards::migrate_payment_method(state, req)).await },
+        &auth::AdminApiAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodSave))]
 pub async fn save_payment_method_api(
     state: web::Data<AppState>,
diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs
index ef92c099735..77873503d6e 100644
--- a/crates/router/src/types/api/payment_methods.rs
+++ b/crates/router/src/types/api/payment_methods.rs
@@ -4,9 +4,10 @@ pub use api_models::payment_methods::{
     GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest,
     PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate,
     PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList,
-    PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodResponse,
-    PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest,
-    TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2,
+    PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate,
+    PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted,
+    TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1,
+    TokenizedWalletValue2,
 };
 use error_stack::report;
 
diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs
index 8f3ef929b1d..49afaf2e474 100644
--- a/crates/router/src/types/transformers.rs
+++ b/crates/router/src/types/transformers.rs
@@ -80,15 +80,25 @@ impl ForeignFrom<api_models::refunds::RefundType> for storage_enums::RefundType
     }
 }
 
-impl ForeignFrom<diesel_models::PaymentMethod> for payment_methods::PaymentMethodResponse {
-    fn foreign_from(item: diesel_models::PaymentMethod) -> Self {
+impl
+    ForeignFrom<(
+        Option<payment_methods::CardDetailFromLocker>,
+        diesel_models::PaymentMethod,
+    )> for payment_methods::PaymentMethodResponse
+{
+    fn foreign_from(
+        (card_details, item): (
+            Option<payment_methods::CardDetailFromLocker>,
+            diesel_models::PaymentMethod,
+        ),
+    ) -> Self {
         Self {
             merchant_id: item.merchant_id,
             customer_id: Some(item.customer_id),
             payment_method_id: item.payment_method_id,
             payment_method: item.payment_method,
             payment_method_type: item.payment_method_type,
-            card: None,
+            card: card_details,
             recurring_enabled: false,
             installment_payment_enabled: false,
             payment_experience: None,
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 733876d0ee6..2fef21d420c 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -111,6 +111,8 @@ pub enum Flow {
     MandatesList,
     /// Payment methods create flow.
     PaymentMethodsCreate,
+    /// Payment methods migrate flow.
+    PaymentMethodsMigrate,
     /// Payment methods list flow.
     PaymentMethodsList,
     /// Payment method save flow

## 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 is the API that will be used to migrate card details and associated data stored in another service to Hyperswitch.

How does this API differ from the existing payment method creation API?

1. It takes masked card details as input with valid first six and last four card numbers.
2. It also accepts `connector_mandate_details` and `network_transaction_id` if present.

If the masked card number is provided as input, a payment method entry is created in Hyperswitch, and no data is stored in the locker. If a valid card number is present, the card details are stored in the locker, the same as in the save card flow.

### 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)?
-->
-> Create merchant connector account for cybersource
### Test on_session saved payment
-> Crate a payment with `setup_future_usage: on_session`
```
{
    "amount": 100,
    "amount_to_capture": 100,
    "currency": "USD",
    "confirm": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "customer_id": "cu_{{$timestamp}}",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "setup_future_usage": "on_session",
    "return_url": "http://127.0.0.1:4040",
    "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "in sit",
            "user_agent": "amet irure esse"
        }
    },
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "card_cvc": "838"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX",
            "last_name": "ss"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX"
        }
    },
    "browser_info": {
        "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/70.0.3538.110 Safari\/537.36",
        "accept_header": "text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,image\/apng,*\/*;q=0.8",
        "language": "nl-NL",
        "color_depth": 24,
        "screen_height": 723,
        "screen_width": 1536,
        "time_zone": 0,
        "java_enabled": true,
        "java_script_enabled": true,
        "ip_address": "125.0.0.1"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}
```
<img width="1105" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/35151830-ba01-45dc-992b-aa7482e4c455">

-> Create payment with confirm false for the same customer
```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_YsyZ2fWnqbVilsKF7TKSUd0Wuwdgf2vvxQzQVIBaCqeSGU9XRY3Pjmk9mLjNsaRm' \
--data '{
  "amount": 10000,
  "currency": "USD",
  "capture_method": "automatic",
  "authentication_type": "no_three_ds",
  "confirm": false,
  "customer_id": "cu_1720508543"
}'
```
<img width="1204" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/5acdd2fd-370b-4e64-a768-e3b44839814b">

-> Do payment method list with the client_secret
```
curl --location 'http://localhost:8080/customers/payment_methods?client_secret=pay_kGh4EiDR0G5PliuQVvcn_secret_SGva6IBvDkYjWGzkORVU' \
--header 'Accept: application/json' \
--header 'api-key: pk_dev_cad52206021b4f44932e750bb3afb1d4'
```
<img width="1190" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/ed3dd62c-f05b-4048-aad4-eda74bf32f5d">

-> Confirm the payment with the payment_token
```
curl --location 'http://localhost:8080/payments/pay_kGh4EiDR0G5PliuQVvcn/confirm' \
--header 'api-key: pk_dev_cad52206021b4f44932e750bb3afb1d4' \
--header 'Content-Type: application/json' \
--data '{
    "payment_token": "token_8hR80yevZdbKLaW2rrY4",
    "client_secret": "pay_kGh4EiDR0G5PliuQVvcn_secret_SGva6IBvDkYjWGzkORVU",
    "payment_method": "card",
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX",
            "last_name": "ss"
        }
    }
}'
```
<img width="1217" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/fdd4fc09-0acc-4e79-b1e9-5fd874af99a3">

### Test off_session saved payment
-> Create payment with setup_future_usage: off_session
```
{
    "amount": 100,
    "amount_to_capture": 100,
    "currency": "USD",
    "confirm": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "customer_id": "cu_{{$timestamp}}",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "setup_future_usage": "off_session",
    "return_url": "http://127.0.0.1:4040",
    "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "in sit",
            "user_agent": "amet irure esse"
        }
    },
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "card_cvc": "838"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX",
            "last_name": "ss"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX"
        }
    },
    "browser_info": {
        "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/70.0.3538.110 Safari\/537.36",
        "accept_header": "text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,image\/apng,*\/*;q=0.8",
        "language": "nl-NL",
        "color_depth": 24,
        "screen_height": 723,
        "screen_width": 1536,
        "time_zone": 0,
        "java_enabled": true,
        "java_script_enabled": true,
        "ip_address": "125.0.0.1"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}
```
<img width="1085" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/233766ef-b390-4b0a-94ef-fbcb562af3e7">

-> Create a payment and payment method list with the client_secret
```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_YsyZ2fWnqbVilsKF7TKSUd0Wuwdgf2vvxQzQVIBaCqeSGU9XRY3Pjmk9mLjNsaRm' \
--data '{
  "amount": 10000,
  "currency": "USD",
  "capture_method": "automatic",
  "authentication_type": "no_three_ds",
  "confirm": false,
  "customer_id": "cu_1720509577"
}'
```
```
curl --location 'http://localhost:8080/customers/payment_methods?client_secret=pay_4QhSPVojFUcfdK2HxncL_secret_4EwPFiS6npXTWyv9eQd9' \
--header 'Accept: application/json' \
--header 'api-key: pk_dev_cad52206021b4f44932e750bb3afb1d4'
```
<img width="1024" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/45bd6716-7aae-426f-81b2-0ee9e8a2c7e7">

-> Confirm the payment with token
```
curl --location 'http://localhost:8080/payments/pay_JuR2NJ9Xw3aOwVKBF7UL/confirm' \
--header 'api-key: pk_dev_cad52206021b4f44932e750bb3afb1d4' \
--header 'Content-Type: application/json' \
--data '{
    "payment_token": "token_aQehaaeF9Z6HSgPjCrPJ",
    "client_secret": "pay_JuR2NJ9Xw3aOwVKBF7UL_secret_k7d6s0Wqn8qcY6iNB2Ti",
    "payment_method": "card",
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX",
            "last_name": "ss"
        }
    }
}'
```
<img width="1051" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/a59bef5f-eebf-4dfd-ba44-8c62fec2ea51">

### Test payment method migration api with connector mandate details
-> use the appropriate `connector_mandate_id` save the above payment method for a customer
```
curl --location 'http://localhost:8080/payment_methods/merchant_1720508278/migrate' \
--header 'api-key: test_admin' \
--header 'Content-Type: application/json' \
--data '{
    "payment_method": "card",
    "card": {
        "card_number": "424242xxxxxx4242",
        "card_exp_month": "10",
        "card_exp_year": "26",
        "nick_name": "Joh"
    },
    "customer_id": "cu_1720456680",
    "connector_mandate_details": {
        "mca_5opSKiHdcALo37nP7Gl": {
            "connector_mandate_id": "connector_mandate_id",
            "original_payment_authorized_amount": 6560,
            "original_payment_authorized_currency": "USD"
        }
    }
}
'
```
-> Create a payment for the customer with confirm false and perform payment
```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_YsyZ2fWnqbVilsKF7TKSUd0Wuwdgf2vvxQzQVIBaCqeSGU9XRY3Pjmk9mLjNsaRm' \
--data '{
  "amount": 10000,
  "currency": "USD",
  "capture_method": "automatic",
  "authentication_type": "no_three_ds",
  "confirm": false,
  "setup_future_usage": "off_session",
  "customer_id": "cu_1720510734"
}'
```
-> Payment method list with the client secret
```
curl --location 'http://localhost:8080/customers/payment_methods?client_secret=pay_nVu4pFzZkJyzIpsZzogt_secret_RU1A52ZevqUfCHsnxSQo' \
--header 'Accept: application/json' \
--header 'api-key: pk_dev_cad52206021b4f44932e750bb3afb1d4'
```
<img width="1096" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/38866b0f-02d7-4f04-ab70-7aaa30864322">

-> Confirm the payment with the token, in this case payment will go through even without the card details as the connector mandate details are present
```
curl --location 'http://localhost:8080/payments/pay_nVu4pFzZkJyzIpsZzogt/confirm' \
--header 'api-key: pk_dev_cad52206021b4f44932e750bb3afb1d4' \
--header 'Content-Type: application/json' \
--data '{
    "payment_token": "token_vm9vZGkoxH2dcWUpsHou",
    "client_secret": "pay_nVu4pFzZkJyzIpsZzogt_secret_RU1A52ZevqUfCHsnxSQo",
    "payment_method": "card",
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX",
            "last_name": "ss"
        }
    }
}'
```

### Test payment method migration api with network_transaction_id
```
curl --location 'http://localhost:8080/payment_methods/migrate' \
--header 'api-key: test_admin' \
--header 'Content-Type: application/json' \
--data '{
    "payment_method": "card",
    "merchant_id": "merchant_1720508278",
    "card": {
        "card_number": "4242424242424242",
        "card_exp_month": "10",
        "card_exp_year": "26",
        "nick_name": "Joh"
    },
    "customer_id": "cu_1720514044",
    "network_transaction_id": "network_transaction_id",
    "connector_mandate_details": {
        "mca_jqWPPZvDec4Y6KLWzgXe": {
            "connector_mandate_id": "connector_mandate_id",
            "original_payment_authorized_amount": 6560,
            "original_payment_authorized_currency": "USD"
        }
    }
}
'
```
-> Enable the is_connector_agnostic_mit_enabled for the business profile
```
curl --location 'http://localhost:8080/account/merchant_1720508278/business_profile/pro_7cMwkt3td8JW7UcwKcAq' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data '{
    "is_connector_agnostic_mit_enabled": true
}'
```
-> Create payment with confirm false
```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_YsyZ2fWnqbVilsKF7TKSUd0Wuwdgf2vvxQzQVIBaCqeSGU9XRY3Pjmk9mLjNsaRm' \
--data '{
  "amount": 10000,
  "currency": "USD",
  "capture_method": "automatic",
  "authentication_type": "no_three_ds",
  "confirm": false,
  "setup_future_usage": "off_session",
  "customer_id": "cu_1720514044"
}'
```
-> list payment methods for customer
```
curl --location 'http://localhost:8080/customers/payment_methods?client_secret=pay_CjuYS0q3tS2huyVZdhQy_secret_AY0QHOox9y6WWeMA0Q07' \
--header 'Accept: application/json' \
--header 'api-key: pk_dev_cad52206021b4f44932e750bb3afb1d4'
```
<img width="1138" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/028c415c-2f80-4703-aed3-6281f8a5e7df">

-> Confirm the payment with the token
```
curl --location 'http://localhost:8080/payments/pay_CjuYS0q3tS2huyVZdhQy/confirm' \
--header 'api-key: pk_dev_cad52206021b4f44932e750bb3afb1d4' \
--header 'Content-Type: application/json' \
--data '{
    "payment_token": "token_a0AggkExh1ReYxueRT6n",
    "client_secret": "pay_CjuYS0q3tS2huyVZdhQy_secret_AY0QHOox9y6WWeMA0Q07",
    "payment_method": "card",
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX",
            "last_name": "ss"
        }
    }
}'
```
<img width="951" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/7ff612a4-a3d9-4b4f-8a50-bb26f75a4500">


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