impl PaymentMethodsController for PmCards<'_> {
    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    #[allow(clippy::too_many_arguments)]
    async fn create_payment_method(
        &self,
        req: &api::PaymentMethodCreate,
        customer_id: &id_type::CustomerId,
        payment_method_id: &str,
        locker_id: Option<String>,
        merchant_id: &id_type::MerchantId,
        pm_metadata: Option<serde_json::Value>,
        customer_acceptance: Option<serde_json::Value>,
        payment_method_data: crypto::OptionalEncryptableValue,
        connector_mandate_details: Option<serde_json::Value>,
        status: Option<enums::PaymentMethodStatus>,
        network_transaction_id: Option<String>,
        payment_method_billing_address: crypto::OptionalEncryptableValue,
        card_scheme: Option<String>,
        network_token_requestor_reference_id: Option<String>,
        network_token_locker_id: Option<String>,
        network_token_payment_method_data: crypto::OptionalEncryptableValue,
    ) -> errors::CustomResult<domain::PaymentMethod, errors::ApiErrorResponse> {
        let db = &*self.state.store;
        let customer = db
            .find_customer_by_customer_id_merchant_id(
                &self.state.into(),
                customer_id,
                merchant_id,
                self.merchant_context.get_merchant_key_store(),
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;

        let client_secret = generate_id(
            consts::ID_LENGTH,
            format!("{payment_method_id}_secret").as_str(),
        );

        let current_time = common_utils::date_time::now();

        let response = db
            .insert_payment_method(
                &self.state.into(),
                self.merchant_context.get_merchant_key_store(),
                domain::PaymentMethod {
                    customer_id: customer_id.to_owned(),
                    merchant_id: merchant_id.to_owned(),
                    payment_method_id: payment_method_id.to_string(),
                    locker_id,
                    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().or(card_scheme),
                    metadata: pm_metadata.map(Secret::new),
                    payment_method_data,
                    connector_mandate_details,
                    customer_acceptance: customer_acceptance.map(Secret::new),
                    client_secret: Some(client_secret),
                    status: status.unwrap_or(enums::PaymentMethodStatus::Active),
                    network_transaction_id: network_transaction_id.to_owned(),
                    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,
                    version: common_types::consts::API_VERSION,
                    network_token_requestor_reference_id,
                    network_token_locker_id,
                    network_token_payment_method_data,
                },
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to add payment method in db")?;

        if customer.default_payment_method_id.is_none() && req.payment_method.is_some() {
            let _ = self
                .set_default_payment_method(merchant_id, customer_id, payment_method_id.to_owned())
                .await
                .map_err(|error| {
                    logger::error!(?error, "Failed to set the payment method as default")
                });
        }
        Ok(response)
    }
    #[cfg(feature = "v1")]
    fn store_default_payment_method(
        &self,
        req: &api::PaymentMethodCreate,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
    ) -> (
        api::PaymentMethodResponse,
        Option<payment_methods::DataDuplicationCheck>,
    ) {
        let pm_id = generate_id(consts::ID_LENGTH, "pm");
        let payment_method_response = api::PaymentMethodResponse {
            merchant_id: merchant_id.to_owned(),
            customer_id: Some(customer_id.to_owned()),
            payment_method_id: pm_id,
            payment_method: req.payment_method,
            payment_method_type: req.payment_method_type,
            #[cfg(feature = "payouts")]
            bank_transfer: None,
            card: None,
            metadata: req.metadata.clone(),
            created: Some(common_utils::date_time::now()),
            recurring_enabled: Some(false),           //[#219]
            installment_payment_enabled: Some(false), //[#219]
            payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
            last_used_at: Some(common_utils::date_time::now()),
            client_secret: None,
        };

        (payment_method_response, None)
    }

    #[cfg(feature = "v2")]
    fn store_default_payment_method(
        &self,
        _req: &api::PaymentMethodCreate,
        _customer_id: &id_type::CustomerId,
        _merchant_id: &id_type::MerchantId,
    ) -> (
        api::PaymentMethodResponse,
        Option<payment_methods::DataDuplicationCheck>,
    ) {
        todo!()
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    async fn get_or_insert_payment_method(
        &self,
        req: api::PaymentMethodCreate,
        resp: &mut api::PaymentMethodResponse,
        customer_id: &id_type::CustomerId,
        key_store: &domain::MerchantKeyStore,
    ) -> errors::RouterResult<domain::PaymentMethod> {
        let mut payment_method_id = resp.payment_method_id.clone();
        let mut locker_id = None;
        let db = &*self.state.store;
        let key_manager_state = &(self.state.into());
        let payment_method = {
            let existing_pm_by_pmid = db
                .find_payment_method(
                    key_manager_state,
                    key_store,
                    &payment_method_id,
                    self.merchant_context.get_merchant_account().storage_scheme,
                )
                .await;

            if let Err(err) = existing_pm_by_pmid {
                if err.current_context().is_db_not_found() {
                    locker_id = Some(payment_method_id.clone());
                    let existing_pm_by_locker_id = db
                        .find_payment_method_by_locker_id(
                            key_manager_state,
                            key_store,
                            &payment_method_id,
                            self.merchant_context.get_merchant_account().storage_scheme,
                        )
                        .await;

                    match &existing_pm_by_locker_id {
                        Ok(pm) => payment_method_id.clone_from(pm.get_id()),
                        Err(_) => payment_method_id = generate_id(consts::ID_LENGTH, "pm"),
                    };
                    existing_pm_by_locker_id
                } else {
                    Err(err)
                }
            } else {
                existing_pm_by_pmid
            }
        };
        payment_method_id.clone_into(&mut resp.payment_method_id);

        match payment_method {
            Ok(pm) => Ok(pm),
            Err(err) => {
                if err.current_context().is_db_not_found() {
                    self.insert_payment_method(
                        resp,
                        &req,
                        key_store,
                        self.merchant_context.get_merchant_account().get_id(),
                        customer_id,
                        resp.metadata.clone().map(|val| val.expose()),
                        None,
                        locker_id,
                        None,
                        req.network_transaction_id.clone(),
                        None,
                        None,
                        None,
                        None,
                    )
                    .await
                } else {
                    Err(err)
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Error while finding payment method")
                }
            }
        }
    }

    #[cfg(feature = "v2")]
    async fn get_or_insert_payment_method(
        &self,
        _req: api::PaymentMethodCreate,
        _resp: &mut api::PaymentMethodResponse,
        _customer_id: &id_type::CustomerId,
        _key_store: &domain::MerchantKeyStore,
    ) -> errors::RouterResult<domain::PaymentMethod> {
        todo!()
    }

    #[cfg(feature = "v1")]
    #[allow(clippy::too_many_arguments)]
    async fn save_network_token_and_update_payment_method(
        &self,
        req: &api::PaymentMethodMigrate,
        key_store: &domain::MerchantKeyStore,
        network_token_data: &api_models::payment_methods::MigrateNetworkTokenData,
        network_token_requestor_ref_id: String,
        pm_id: String,
    ) -> errors::RouterResult<bool> {
        let payment_method_create_request =
            api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate(
                network_token_data.network_token_number.clone(),
                req,
            );
        let customer_id = req.customer_id.clone().get_required_value("customer_id")?;

        let network_token_details = api::CardDetail {
            card_number: network_token_data.network_token_number.clone(),
            card_exp_month: network_token_data.network_token_exp_month.clone(),
            card_exp_year: network_token_data.network_token_exp_year.clone(),
            card_holder_name: network_token_data.card_holder_name.clone(),
            nick_name: network_token_data.nick_name.clone(),
            card_issuing_country: network_token_data.card_issuing_country.clone(),
            card_network: network_token_data.card_network.clone(),
            card_issuer: network_token_data.card_issuer.clone(),
            card_type: network_token_data.card_type.clone(),
        };

        logger::debug!(
            "Adding network token to locker for customer_id: {:?}",
            customer_id
        );

        let token_resp = Box::pin(self.add_card_to_locker(
            payment_method_create_request.clone(),
            &network_token_details,
            &customer_id,
            None,
        ))
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Add Network Token failed");
        let key_manager_state = &self.state.into();

        match token_resp {
            Ok(resp) => {
                logger::debug!("Network token added to locker");
                let (token_pm_resp, _duplication_check) = resp;
                let pm_token_details = token_pm_resp.card.as_ref().map(|card| {
                    PaymentMethodsData::Card(CardDetailsPaymentMethod::from((card.clone(), None)))
                });
                let pm_network_token_data_encrypted = pm_token_details
                    .async_map(|pm_card| {
                        create_encrypted_data(key_manager_state, key_store, pm_card)
                    })
                    .await
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Unable to encrypt payment method data")?;

                let pm_update = storage::PaymentMethodUpdate::NetworkTokenDataUpdate {
                    network_token_requestor_reference_id: Some(network_token_requestor_ref_id),
                    network_token_locker_id: Some(token_pm_resp.payment_method_id),
                    network_token_payment_method_data: pm_network_token_data_encrypted
                        .map(Into::into),
                };
                let db = &*self.state.store;
                let existing_pm = db
                    .find_payment_method(
                        &self.state.into(),
                        key_store,
                        &pm_id,
                        self.merchant_context.get_merchant_account().storage_scheme,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(format!(
                        "Failed to fetch payment method for existing pm_id: {pm_id:?} in db",
                    ))?;

                db.update_payment_method(
                    &self.state.into(),
                    key_store,
                    existing_pm,
                    pm_update,
                    self.merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(format!(
                    "Failed to update payment method for existing pm_id: {pm_id:?} in db",
                ))?;

                logger::debug!("Network token added to locker and payment method updated");
                Ok(true)
            }
            Err(err) => {
                logger::debug!("Network token added to locker failed {:?}", err);
                Ok(false)
            }
        }
    }

    #[cfg(feature = "v1")]
    #[allow(clippy::too_many_arguments)]
    async fn insert_payment_method(
        &self,
        resp: &api::PaymentMethodResponse,
        req: &api::PaymentMethodCreate,
        key_store: &domain::MerchantKeyStore,
        merchant_id: &id_type::MerchantId,
        customer_id: &id_type::CustomerId,
        pm_metadata: Option<serde_json::Value>,
        customer_acceptance: Option<serde_json::Value>,
        locker_id: Option<String>,
        connector_mandate_details: Option<serde_json::Value>,
        network_transaction_id: Option<String>,
        payment_method_billing_address: crypto::OptionalEncryptableValue,
        network_token_requestor_reference_id: Option<String>,
        network_token_locker_id: Option<String>,
        network_token_payment_method_data: crypto::OptionalEncryptableValue,
    ) -> errors::RouterResult<domain::PaymentMethod> {
        let pm_card_details = resp.card.clone().map(|card| {
            PaymentMethodsData::Card(CardDetailsPaymentMethod::from((card.clone(), None)))
        });
        let key_manager_state = self.state.into();
        let pm_data_encrypted: crypto::OptionalEncryptableValue = pm_card_details
            .clone()
            .async_map(|pm_card| create_encrypted_data(&key_manager_state, key_store, pm_card))
            .await
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt payment method data")?;

        self.create_payment_method(
            req,
            customer_id,
            &resp.payment_method_id,
            locker_id,
            merchant_id,
            pm_metadata,
            customer_acceptance,
            pm_data_encrypted,
            connector_mandate_details,
            None,
            network_transaction_id,
            payment_method_billing_address,
            resp.card.clone().and_then(|card| {
                card.card_network
                    .map(|card_network| card_network.to_string())
            }),
            network_token_requestor_reference_id,
            network_token_locker_id,
            network_token_payment_method_data,
        )
        .await
    }

    #[cfg(feature = "v2")]
    #[allow(clippy::too_many_arguments)]
    async fn insert_payment_method(
        &self,
        resp: &api::PaymentMethodResponse,
        req: &api::PaymentMethodCreate,
        key_store: &domain::MerchantKeyStore,
        merchant_id: &id_type::MerchantId,
        customer_id: &id_type::CustomerId,
        pm_metadata: Option<serde_json::Value>,
        customer_acceptance: Option<serde_json::Value>,
        locker_id: Option<String>,
        connector_mandate_details: Option<serde_json::Value>,
        network_transaction_id: Option<String>,
        payment_method_billing_address: Option<Encryption>,
    ) -> errors::RouterResult<domain::PaymentMethod> {
        todo!()
    }

    #[cfg(feature = "payouts")]
    async fn add_bank_to_locker(
        &self,
        req: api::PaymentMethodCreate,
        key_store: &domain::MerchantKeyStore,
        bank: &api::BankPayout,
        customer_id: &id_type::CustomerId,
    ) -> errors::CustomResult<
        (
            api::PaymentMethodResponse,
            Option<payment_methods::DataDuplicationCheck>,
        ),
        errors::VaultError,
    > {
        let key = key_store.key.get_inner().peek();
        let payout_method_data = api::PayoutMethodData::Bank(bank.clone());
        let key_manager_state: KeyManagerState = self.state.into();
        let enc_data = async {
            serde_json::to_value(payout_method_data.to_owned())
                .map_err(|err| {
                    logger::error!("Error while encoding payout method data: {err:?}");
                    errors::VaultError::SavePaymentMethodFailed
                })
                .change_context(errors::VaultError::SavePaymentMethodFailed)
                .attach_printable("Unable to encode payout method data")
                .ok()
                .map(|v| {
                    let secret: Secret<String> = Secret::new(v.to_string());
                    secret
                })
                .async_lift(|inner| async {
                    domain::types::crypto_operation(
                        &key_manager_state,
                        type_name!(payment_method::PaymentMethod),
                        domain::types::CryptoOperation::EncryptOptional(inner),
                        Identifier::Merchant(key_store.merchant_id.clone()),
                        key,
                    )
                    .await
                    .and_then(|val| val.try_into_optionaloperation())
                })
                .await
        }
        .await
        .change_context(errors::VaultError::SavePaymentMethodFailed)
        .attach_printable("Failed to encrypt payout method data")?
        .map(Encryption::from)
        .map(|e| e.into_inner())
        .map_or(Err(errors::VaultError::SavePaymentMethodFailed), |e| {
            Ok(hex::encode(e.peek()))
        })?;

        let payload =
            payment_methods::StoreLockerReq::LockerGeneric(payment_methods::StoreGenericReq {
                merchant_id: self
                    .merchant_context
                    .get_merchant_account()
                    .get_id()
                    .to_owned(),
                merchant_customer_id: customer_id.to_owned(),
                enc_data,
                ttl: self.state.conf.locker.ttl_for_storage_in_secs,
            });
        let store_resp = add_card_to_hs_locker(
            self.state,
            &payload,
            customer_id,
            api_enums::LockerChoice::HyperswitchCardVault,
        )
        .await?;
        let payment_method_resp = payment_methods::mk_add_bank_response_hs(
            bank.clone(),
            store_resp.card_reference,
            req,
            self.merchant_context.get_merchant_account().get_id(),
        );
        Ok((payment_method_resp, store_resp.duplication_check))
    }

    /// The response will be the tuple of PaymentMethodResponse and the duplication check of payment_method
    async fn add_card_to_locker(
        &self,
        req: api::PaymentMethodCreate,
        card: &api::CardDetail,
        customer_id: &id_type::CustomerId,
        card_reference: Option<&str>,
    ) -> errors::CustomResult<
        (
            api::PaymentMethodResponse,
            Option<payment_methods::DataDuplicationCheck>,
        ),
        errors::VaultError,
    > {
        metrics::STORED_TO_LOCKER.add(1, &[]);
        let add_card_to_hs_resp = Box::pin(common_utils::metrics::utils::record_operation_time(
            async {
                self.add_card_hs(
                    req.clone(),
                    card,
                    customer_id,
                    api_enums::LockerChoice::HyperswitchCardVault,
                    card_reference,
                )
                .await
                .inspect_err(|_| {
                    metrics::CARD_LOCKER_FAILURES.add(
                        1,
                        router_env::metric_attributes!(("locker", "rust"), ("operation", "add")),
                    );
                })
            },
            &metrics::CARD_ADD_TIME,
            router_env::metric_attributes!(("locker", "rust")),
        ))
        .await?;

        logger::debug!("card added to hyperswitch-card-vault");
        Ok(add_card_to_hs_resp)
    }

    async fn delete_card_from_locker(
        &self,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
        card_reference: &str,
    ) -> errors::RouterResult<payment_methods::DeleteCardResp> {
        metrics::DELETE_FROM_LOCKER.add(1, &[]);

        common_utils::metrics::utils::record_operation_time(
            async move {
                delete_card_from_hs_locker(self.state, customer_id, merchant_id, card_reference)
                    .await
                    .inspect_err(|_| {
                        metrics::CARD_LOCKER_FAILURES.add(
                            1,
                            router_env::metric_attributes!(
                                ("locker", "rust"),
                                ("operation", "delete")
                            ),
                        );
                    })
            },
            &metrics::CARD_DELETE_TIME,
            &[],
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while deleting card from locker")
    }

    #[instrument(skip_all)]
    async fn add_card_hs(
        &self,
        req: api::PaymentMethodCreate,
        card: &api::CardDetail,
        customer_id: &id_type::CustomerId,
        locker_choice: api_enums::LockerChoice,
        card_reference: Option<&str>,
    ) -> errors::CustomResult<
        (
            api::PaymentMethodResponse,
            Option<payment_methods::DataDuplicationCheck>,
        ),
        errors::VaultError,
    > {
        let payload = payment_methods::StoreLockerReq::LockerCard(payment_methods::StoreCardReq {
            merchant_id: self
                .merchant_context
                .get_merchant_account()
                .get_id()
                .to_owned(),
            merchant_customer_id: customer_id.to_owned(),
            requestor_card_reference: card_reference.map(str::to_string),
            card: Card {
                card_number: card.card_number.to_owned(),
                name_on_card: card.card_holder_name.to_owned(),
                card_exp_month: card.card_exp_month.to_owned(),
                card_exp_year: card.card_exp_year.to_owned(),
                card_brand: card.card_network.as_ref().map(ToString::to_string),
                card_isin: None,
                nick_name: card.nick_name.as_ref().map(Secret::peek).cloned(),
            },
            ttl: self.state.conf.locker.ttl_for_storage_in_secs,
        });

        let store_card_payload =
            add_card_to_hs_locker(self.state, &payload, customer_id, locker_choice).await?;

        let payment_method_resp = payment_methods::mk_add_card_response_hs(
            card.clone(),
            store_card_payload.card_reference,
            req,
            self.merchant_context.get_merchant_account().get_id(),
        );
        Ok((payment_method_resp, store_card_payload.duplication_check))
    }

    #[cfg(feature = "v1")]
    async fn get_card_details_with_locker_fallback(
        &self,
        pm: &domain::PaymentMethod,
    ) -> errors::RouterResult<Option<api::CardDetailFromLocker>> {
        let card_decrypted = pm
            .payment_method_data
            .clone()
            .map(|x| x.into_inner().expose())
            .and_then(|v| serde_json::from_value::<PaymentMethodsData>(v).ok())
            .and_then(|pmd| match pmd {
                PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)),
                _ => None,
            });

        Ok(if let Some(mut crd) = card_decrypted {
            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"
            );
            Some(get_card_details_from_locker(self.state, pm).await?)
        })
    }

    #[cfg(feature = "v1")]
    async fn get_card_details_without_locker_fallback(
        &self,
        pm: &domain::PaymentMethod,
    ) -> errors::RouterResult<api::CardDetailFromLocker> {
        let card_decrypted = pm
            .payment_method_data
            .clone()
            .map(|x| x.into_inner().expose())
            .and_then(|v| serde_json::from_value::<PaymentMethodsData>(v).ok())
            .and_then(|pmd| match pmd {
                PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)),
                _ => None,
            });

        Ok(if let Some(mut crd) = card_decrypted {
            crd.scheme.clone_from(&pm.scheme);
            crd
        } else {
            logger::debug!(
                "Getting card details from locker as it is not found in payment methods table"
            );
            get_card_details_from_locker(self.state, pm).await?
        })
    }

    #[cfg(feature = "v1")]
    async fn set_default_payment_method(
        &self,
        merchant_id: &id_type::MerchantId,
        customer_id: &id_type::CustomerId,
        payment_method_id: String,
    ) -> errors::RouterResponse<api_models::payment_methods::CustomerDefaultPaymentMethodResponse>
    {
        let db = &*self.state.store;
        let key_manager_state = &self.state.into();
        // check for the customer
        // TODO: customer need not be checked again here, this function can take an optional customer and check for existence of customer based on the optional value
        let customer = db
            .find_customer_by_customer_id_merchant_id(
                key_manager_state,
                customer_id,
                merchant_id,
                self.merchant_context.get_merchant_key_store(),
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;
        // check for the presence of payment_method
        let payment_method = db
            .find_payment_method(
                &(self.state.into()),
                self.merchant_context.get_merchant_key_store(),
                &payment_method_id,
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
        let pm = payment_method
            .get_payment_method_type()
            .get_required_value("payment_method")?;

        utils::when(
            &payment_method.customer_id != customer_id
                || payment_method.merchant_id != *merchant_id,
            || {
                Err(errors::ApiErrorResponse::PreconditionFailed {
                    message: "The payment_method_id is not valid".to_string(),
                })
            },
        )?;

        utils::when(
            Some(payment_method_id.clone()) == customer.default_payment_method_id,
            || {
                Err(errors::ApiErrorResponse::PreconditionFailed {
                    message: "Payment Method is already set as default".to_string(),
                })
            },
        )?;

        let customer_id = customer.customer_id.clone();

        let customer_update = CustomerUpdate::UpdateDefaultPaymentMethod {
            default_payment_method_id: Some(Some(payment_method_id.to_owned())),
        };
        // update the db with the default payment method id

        let updated_customer_details = db
            .update_customer_by_customer_id_merchant_id(
                key_manager_state,
                customer_id.to_owned(),
                merchant_id.to_owned(),
                customer,
                customer_update,
                self.merchant_context.get_merchant_key_store(),
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to update the default payment method id for the customer")?;

        let resp = api_models::payment_methods::CustomerDefaultPaymentMethodResponse {
            default_payment_method_id: updated_customer_details.default_payment_method_id,
            customer_id,
            payment_method_type: payment_method.get_payment_method_subtype(),
            payment_method: pm,
        };

        Ok(services::ApplicationResponse::Json(resp))
    }

    #[cfg(feature = "v1")]
    async fn add_payment_method_status_update_task(
        &self,
        payment_method: &domain::PaymentMethod,
        prev_status: common_enums::PaymentMethodStatus,
        curr_status: common_enums::PaymentMethodStatus,
        merchant_id: &id_type::MerchantId,
    ) -> Result<(), sch_errors::ProcessTrackerError> {
        add_payment_method_status_update_task(
            &*self.state.store,
            payment_method,
            prev_status,
            curr_status,
            merchant_id,
        )
        .await
    }

    #[cfg(feature = "v1")]
    async fn validate_merchant_connector_ids_in_connector_mandate_details(
        &self,
        key_store: &domain::MerchantKeyStore,
        connector_mandate_details: &api_models::payment_methods::CommonMandateReference,
        merchant_id: &id_type::MerchantId,
        card_network: Option<common_enums::CardNetwork>,
    ) -> errors::RouterResult<()> {
        helpers::validate_merchant_connector_ids_in_connector_mandate_details(
            self.state,
            key_store,
            connector_mandate_details,
            merchant_id,
            card_network,
        )
        .await
    }

    #[cfg(feature = "v1")]
    async fn get_card_details_from_locker(
        &self,
        pm: &domain::PaymentMethod,
    ) -> errors::RouterResult<api::CardDetailFromLocker> {
        get_card_details_from_locker(self.state, pm).await
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    async fn retrieve_payment_method(
        &self,
        pm: api::PaymentMethodId,
    ) -> errors::RouterResponse<api::PaymentMethodResponse> {
        let db = self.state.store.as_ref();
        let pm = db
            .find_payment_method(
                &self.state.into(),
                self.merchant_context.get_merchant_key_store(),
                &pm.payment_method_id,
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;

        let card = if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) {
            let card_detail = if self.state.conf.locker.locker_enabled {
                let card = get_card_from_locker(
                    self.state,
                    &pm.customer_id,
                    &pm.merchant_id,
                    pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error getting card from card vault")?;
                payment_methods::get_card_detail(&pm, card)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed while getting card details from locker")?
            } else {
                self.get_card_details_without_locker_fallback(&pm).await?
            };
            Some(card_detail)
        } else {
            None
        };
        Ok(services::ApplicationResponse::Json(
            api::PaymentMethodResponse {
                merchant_id: pm.merchant_id.clone(),
                customer_id: Some(pm.customer_id.clone()),
                payment_method_id: pm.payment_method_id.clone(),
                payment_method: pm.get_payment_method_type(),
                payment_method_type: pm.get_payment_method_subtype(),
                #[cfg(feature = "payouts")]
                bank_transfer: None,
                card,
                metadata: pm.metadata,
                created: Some(pm.created_at),
                recurring_enabled: Some(false),
                installment_payment_enabled: Some(false),
                payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
                last_used_at: Some(pm.last_used_at),
                client_secret: pm.client_secret,
            },
        ))
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    async fn delete_payment_method(
        &self,
        pm_id: api::PaymentMethodId,
    ) -> errors::RouterResponse<api::PaymentMethodDeleteResponse> {
        let db = self.state.store.as_ref();
        let key_manager_state = &(self.state.into());
        let key = db
            .find_payment_method(
                key_manager_state,
                self.merchant_context.get_merchant_key_store(),
                pm_id.payment_method_id.as_str(),
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;

        let customer = db
            .find_customer_by_customer_id_merchant_id(
                key_manager_state,
                &key.customer_id,
                self.merchant_context.get_merchant_account().get_id(),
                self.merchant_context.get_merchant_key_store(),
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Customer not found for the payment method")?;

        if key.get_payment_method_type() == Some(enums::PaymentMethod::Card) {
            let response = self
                .delete_card_from_locker(
                    &key.customer_id,
                    &key.merchant_id,
                    key.locker_id.as_ref().unwrap_or(&key.payment_method_id),
                )
                .await?;

            if let Some(network_token_ref_id) = key.network_token_requestor_reference_id {
                let resp =
                    network_tokenization::delete_network_token_from_locker_and_token_service(
                        self.state,
                        &key.customer_id,
                        &key.merchant_id,
                        key.payment_method_id.clone(),
                        key.network_token_locker_id,
                        network_token_ref_id,
                        self.merchant_context,
                    )
                    .await?;

                if resp.status == "Ok" {
                    logger::info!("Token From locker deleted Successfully!");
                } else {
                    logger::error!("Error: Deleting Token From Locker!\n{:#?}", resp);
                }
            }

            if response.status == "Ok" {
                logger::info!("Card From locker deleted Successfully!");
            } else {
                logger::error!("Error: Deleting Card From Locker!\n{:#?}", response);
                Err(errors::ApiErrorResponse::InternalServerError)?
            }
        }

        db.delete_payment_method_by_merchant_id_payment_method_id(
            &(self.state.into()),
            self.merchant_context.get_merchant_key_store(),
            self.merchant_context.get_merchant_account().get_id(),
            pm_id.payment_method_id.as_str(),
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;

        if customer.default_payment_method_id.as_ref() == Some(&pm_id.payment_method_id) {
            let customer_update = CustomerUpdate::UpdateDefaultPaymentMethod {
                default_payment_method_id: Some(None),
            };
            db.update_customer_by_customer_id_merchant_id(
                key_manager_state,
                key.customer_id,
                key.merchant_id,
                customer,
                customer_update,
                self.merchant_context.get_merchant_key_store(),
                self.merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to update the default payment method id for the customer")?;
        };

        Ok(services::ApplicationResponse::Json(
            api::PaymentMethodDeleteResponse {
                payment_method_id: key.payment_method_id.clone(),
                deleted: true,
            },
        ))
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    async fn add_payment_method(
        &self,
        req: &api::PaymentMethodCreate,
    ) -> errors::RouterResponse<api::PaymentMethodResponse> {
        req.validate()?;
        let db = &*self.state.store;
        let merchant_id = self.merchant_context.get_merchant_account().get_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 key_manager_state = self.state.into();
        let payment_method_billing_address: Option<Encryptable<Secret<serde_json::Value>>> = req
            .billing
            .clone()
            .async_map(|billing| {
                create_encrypted_data(
                    &key_manager_state,
                    self.merchant_context.get_merchant_key_store(),
                    billing,
                )
            })
            .await
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt Payment method billing address")?;

        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")]
            api_enums::PaymentMethod::BankTransfer => match req.bank_transfer.clone() {
                Some(bank) => self
                    .add_bank_to_locker(
                        req.clone(),
                        self.merchant_context.get_merchant_key_store(),
                        &bank,
                        &customer_id,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Add PaymentMethod Failed"),
                _ => Ok(self.store_default_payment_method(req, &customer_id, merchant_id)),
            },
            api_enums::PaymentMethod::Card => match req.card.clone() {
                Some(card) => {
                    let mut card_details = card;
                    card_details = helpers::populate_bin_details_for_payment_method_create(
                        card_details.clone(),
                        db.get_payment_methods_store(),
                    )
                    .await;
                    helpers::validate_card_expiry(
                        &card_details.card_exp_month,
                        &card_details.card_exp_year,
                    )?;
                    Box::pin(self.add_card_to_locker(
                        req.clone(),
                        &card_details,
                        &customer_id,
                        None,
                    ))
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Add Card Failed")
                }
                _ => Ok(self.store_default_payment_method(req, &customer_id, merchant_id)),
            },
            _ => Ok(self.store_default_payment_method(req, &customer_id, merchant_id)),
        };

        let (mut resp, duplication_check) = response?;

        match duplication_check {
            Some(duplication_check) => match duplication_check {
                payment_methods::DataDuplicationCheck::Duplicated => {
                    let existing_pm = self
                        .get_or_insert_payment_method(
                            req.clone(),
                            &mut resp,
                            &customer_id,
                            self.merchant_context.get_merchant_key_store(),
                        )
                        .await?;

                    resp.client_secret = existing_pm.client_secret;
                }
                payment_methods::DataDuplicationCheck::MetaDataChanged => {
                    if let Some(card) = req.card.clone() {
                        let existing_pm = self
                            .get_or_insert_payment_method(
                                req.clone(),
                                &mut resp,
                                &customer_id,
                                self.merchant_context.get_merchant_key_store(),
                            )
                            .await?;

                        let client_secret = existing_pm.client_secret.clone();

                        self.delete_card_from_locker(
                            &customer_id,
                            merchant_id,
                            existing_pm
                                .locker_id
                                .as_ref()
                                .unwrap_or(&existing_pm.payment_method_id),
                        )
                        .await?;

impl PaymentMethodsController for for PmCards


pub struct PaysafeRefundRequest {
    pub merchant_ref_num: String,
    pub amount: MinorUnit,
}


/// Middleware for recording request-response metrics
pub struct RequestResponseMetrics;


OpenAPI Block Path: paths."/accounts"
{
  "post": {
    "tags": [
      "Merchant Account"
    ],
    "summary": "Merchant Account - Create",
    "description": "Create a new account for a *merchant* and the *merchant* could be a seller or retailer or client who likes to receive and send payments.",
    "operationId": "Create a Merchant Account",
    "requestBody": {
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/MerchantAccountCreate"
          },
          "examples": {
            "Create a merchant account with minimal fields": {
              "value": {
                "merchant_id": "merchant_abc"
              }
            },
            "Create a merchant account with return url": {
              "value": {
                "merchant_id": "merchant_abc",
                "return_url": "https://example.com"
              }
            },
            "Create a merchant account with webhook url": {
              "value": {
                "merchant_id": "merchant_abc",
                "webhook_details": {
                  "webhook_url": "https://webhook.site/a5c54f75-1f7e-4545-b781-af525b7e37a0"
                }
              }
            }
          }
        }
      },
      "required": true
    },
    "responses": {
      "200": {
        "description": "Merchant Account Created",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MerchantAccountResponse"
            }
          }
        }
      },
      "400": {
        "description": "Invalid data"
      }
    },
    "security": [
      {
        "admin_api_key": []
      }
    ]
  }
}


impl Flexiti {
    fn get_default_header() -> (String, masking::Maskable<String>) {
        (
            "x-reference-id".to_string(),
            Uuid::new_v4().to_string().into(),
        )
    }
}


pub fn server(state: AppState) -> Scope {
        web::scope("/organization")
            .app_data(web::Data::new(state))
            .service(web::resource("").route(web::post().to(admin::organization_create)))
            .service(
                web::resource("/{id}")
                    .route(web::get().to(admin::organization_retrieve))
                    .route(web::put().to(admin::organization_update)),
            )
    }


impl ConnectorTransactionIdTrait for Capture {
    fn get_optional_connector_transaction_id(&self) -> Option<&String> {
        match self
            .connector_capture_id
            .as_ref()
            .map(|capture_id| capture_id.get_txn_id(self.processor_capture_data.as_ref()))
            .transpose()
        {
            Ok(capture_id) => capture_id,

            // In case hashed data is missing from DB, use the hashed ID as connector transaction ID
            Err(_) => self
                .connector_capture_id
                .as_ref()
                .map(|txn_id| txn_id.get_id()),
        }
    }
}

impl ConnectorTransactionIdTrait for for Capture


/// Validates the file storage configuration.
    pub fn validate(&self) -> Result<(), InvalidFileStorageConfig> {
        match self {
            #[cfg(feature = "aws_s3")]
            Self::AwsS3 { aws_s3 } => aws_s3.validate(),
            Self::FileSystem => Ok(()),
        }
    }


OpenAPI Block Path: components.securitySchemes
{
  "admin_api_key": {
    "type": "apiKey",
    "in": "header",
    "name": "api-key",
    "description": "Admin API keys allow you to perform some privileged actions such as creating a merchant account and Merchant Connector account."
  },
  "api_key": {
    "type": "apiKey",
    "in": "header",
    "name": "api-key",
    "description": "Use the API key created under your merchant account from the HyperSwitch dashboard. API key is used to authenticate API requests from your merchant server only. Don't expose this key on a website or embed it in a mobile application."
  },
  "ephemeral_key": {
    "type": "apiKey",
    "in": "header",
    "name": "api-key",
    "description": "Ephemeral keys provide temporary access to singular data, such as access to a single customer object for a short period of time."
  },
  "jwt_key": {
    "type": "http",
    "scheme": "bearer",
    "bearerFormat": "JWT"
  },
  "publishable_key": {
    "type": "apiKey",
    "in": "header",
    "name": "api-key",
    "description": "Publishable keys are a type of keys that can be public and have limited scope of usage."
  }
}


pub fn log_event<T: KafkaMessage>(&self, event: &T) -> MQResult<()> {
        router_env::logger::debug!("Logging Kafka Event {event:?}");
        let topic = self.get_topic(event.event_type());
        self.producer
            .0
            .send(
                BaseRecord::to(topic)
                    .key(&event.key())
                    .payload(&event.value()?)
                    .timestamp(event.creation_timestamp().unwrap_or_else(|| {
                        (OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000)
                            .try_into()
                            .unwrap_or_else(|_| {
                                // kafka producer accepts milliseconds
                                // try converting nanos to millis if that fails convert seconds to millis
                                OffsetDateTime::now_utc().unix_timestamp() * 1_000
                            })
                    })),
            )
            .map_err(|(error, record)| report!(error).attach_printable(format!("{record:?}")))
            .change_context(KafkaError::GenericError)
    }


pub struct GcashRedirection {}


pub struct CybersourceRsyncResponse {
    id: String,
    application_information: Option<RsyncApplicationInformation>,
    error_information: Option<CybersourceErrorInformation>,
}


pub struct NuveiCardDetails {
    card: payment_method_data::Card,
    three_d: Option<ThreeD>,
    card_holder_name: Option<Secret<String>>,
}


pub struct CustomerDeviceDataInput {
    pub platform: Option<CustomerDevicePlatform>,
    pub device_type: Option<CustomerDeviceType>,
    pub display_size: Option<CustomerDeviceDisplaySize>,
}


pub fn get_connector_name_as_string(&self) -> String {
        self.connector_name.clone()
    }


impl api::PaymentCapture for Deutschebank {}

impl api::PaymentCapture for for Deutschebank


File: crates/hyperswitch_domain_models/src/merchant_account.rs
 Public functions: 7
 Public structs: 4

use common_utils::{
    crypto::{OptionalEncryptableName, OptionalEncryptableValue},
    date_time,
    encryption::Encryption,
    errors::{CustomResult, ValidationError},
    ext_traits::ValueExt,
    pii, type_name,
    types::keymanager::{self},
};
use diesel_models::{
    enums::MerchantStorageScheme, merchant_account::MerchantAccountUpdateInternal,
};
use error_stack::ResultExt;
use masking::{PeekInterface, Secret};
use router_env::logger;

use crate::type_encryption::{crypto_operation, AsyncLift, CryptoOperation};

#[cfg(feature = "v1")]
#[derive(Clone, Debug, serde::Serialize)]
pub struct MerchantAccount {
    merchant_id: common_utils::id_type::MerchantId,
    pub return_url: Option<String>,
    pub enable_payment_response_hash: bool,
    pub payment_response_hash_key: Option<String>,
    pub redirect_to_merchant_with_http_post: bool,
    pub merchant_name: OptionalEncryptableName,
    pub merchant_details: OptionalEncryptableValue,
    pub webhook_details: Option<diesel_models::business_profile::WebhookDetails>,
    pub sub_merchants_enabled: Option<bool>,
    pub parent_merchant_id: Option<common_utils::id_type::MerchantId>,
    pub publishable_key: String,
    pub storage_scheme: MerchantStorageScheme,
    pub locker_id: Option<String>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub routing_algorithm: Option<serde_json::Value>,
    pub primary_business_details: serde_json::Value,
    pub frm_routing_algorithm: Option<serde_json::Value>,
    pub created_at: time::PrimitiveDateTime,
    pub modified_at: time::PrimitiveDateTime,
    pub intent_fulfillment_time: Option<i64>,
    pub payout_routing_algorithm: Option<serde_json::Value>,
    pub organization_id: common_utils::id_type::OrganizationId,
    pub is_recon_enabled: bool,
    pub default_profile: Option<common_utils::id_type::ProfileId>,
    pub recon_status: diesel_models::enums::ReconStatus,
    pub payment_link_config: Option<serde_json::Value>,
    pub pm_collect_link_config: Option<serde_json::Value>,
    pub version: common_enums::ApiVersion,
    pub is_platform_account: bool,
    pub product_type: Option<common_enums::MerchantProductType>,
    pub merchant_account_type: common_enums::MerchantAccountType,
}

#[cfg(feature = "v1")]
#[derive(Clone)]
/// Set the private fields of merchant account
pub struct MerchantAccountSetter {
    pub merchant_id: common_utils::id_type::MerchantId,
    pub return_url: Option<String>,
    pub enable_payment_response_hash: bool,
    pub payment_response_hash_key: Option<String>,
    pub redirect_to_merchant_with_http_post: bool,
    pub merchant_name: OptionalEncryptableName,
    pub merchant_details: OptionalEncryptableValue,
    pub webhook_details: Option<diesel_models::business_profile::WebhookDetails>,
    pub sub_merchants_enabled: Option<bool>,
    pub parent_merchant_id: Option<common_utils::id_type::MerchantId>,
    pub publishable_key: String,
    pub storage_scheme: MerchantStorageScheme,
    pub locker_id: Option<String>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub routing_algorithm: Option<serde_json::Value>,
    pub primary_business_details: serde_json::Value,
    pub frm_routing_algorithm: Option<serde_json::Value>,
    pub created_at: time::PrimitiveDateTime,
    pub modified_at: time::PrimitiveDateTime,
    pub intent_fulfillment_time: Option<i64>,
    pub payout_routing_algorithm: Option<serde_json::Value>,
    pub organization_id: common_utils::id_type::OrganizationId,
    pub is_recon_enabled: bool,
    pub default_profile: Option<common_utils::id_type::ProfileId>,
    pub recon_status: diesel_models::enums::ReconStatus,
    pub payment_link_config: Option<serde_json::Value>,
    pub pm_collect_link_config: Option<serde_json::Value>,
    pub version: common_enums::ApiVersion,
    pub is_platform_account: bool,
    pub product_type: Option<common_enums::MerchantProductType>,
    pub merchant_account_type: common_enums::MerchantAccountType,
}

#[cfg(feature = "v1")]
impl From<MerchantAccountSetter> for MerchantAccount {
    fn from(item: MerchantAccountSetter) -> Self {
        Self {
            merchant_id: item.merchant_id,
            return_url: item.return_url,
            enable_payment_response_hash: item.enable_payment_response_hash,
            payment_response_hash_key: item.payment_response_hash_key,
            redirect_to_merchant_with_http_post: item.redirect_to_merchant_with_http_post,
            merchant_name: item.merchant_name,
            merchant_details: item.merchant_details,
            webhook_details: item.webhook_details,
            sub_merchants_enabled: item.sub_merchants_enabled,
            parent_merchant_id: item.parent_merchant_id,
            publishable_key: item.publishable_key,
            storage_scheme: item.storage_scheme,
            locker_id: item.locker_id,
            metadata: item.metadata,
            routing_algorithm: item.routing_algorithm,
            primary_business_details: item.primary_business_details,
            frm_routing_algorithm: item.frm_routing_algorithm,
            created_at: item.created_at,
            modified_at: item.modified_at,
            intent_fulfillment_time: item.intent_fulfillment_time,
            payout_routing_algorithm: item.payout_routing_algorithm,
            organization_id: item.organization_id,
            is_recon_enabled: item.is_recon_enabled,
            default_profile: item.default_profile,
            recon_status: item.recon_status,
            payment_link_config: item.payment_link_config,
            pm_collect_link_config: item.pm_collect_link_config,
            version: item.version,
            is_platform_account: item.is_platform_account,
            product_type: item.product_type,
            merchant_account_type: item.merchant_account_type,
        }
    }
}

#[cfg(feature = "v2")]
#[derive(Clone)]
/// Set the private fields of merchant account
pub struct MerchantAccountSetter {
    pub id: common_utils::id_type::MerchantId,
    pub merchant_name: OptionalEncryptableName,
    pub merchant_details: OptionalEncryptableValue,
    pub publishable_key: String,
    pub storage_scheme: MerchantStorageScheme,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub created_at: time::PrimitiveDateTime,
    pub modified_at: time::PrimitiveDateTime,
    pub organization_id: common_utils::id_type::OrganizationId,
    pub recon_status: diesel_models::enums::ReconStatus,
    pub is_platform_account: bool,
    pub version: common_enums::ApiVersion,
    pub product_type: Option<common_enums::MerchantProductType>,
    pub merchant_account_type: common_enums::MerchantAccountType,
}

#[cfg(feature = "v2")]
impl From<MerchantAccountSetter> for MerchantAccount {
    fn from(item: MerchantAccountSetter) -> Self {
        let MerchantAccountSetter {
            id,
            merchant_name,
            merchant_details,
            publishable_key,
            storage_scheme,
            metadata,
            created_at,
            modified_at,
            organization_id,
            recon_status,
            is_platform_account,
            version,
            product_type,
            merchant_account_type,
        } = item;
        Self {
            id,
            merchant_name,
            merchant_details,
            publishable_key,
            storage_scheme,
            metadata,
            created_at,
            modified_at,
            organization_id,
            recon_status,
            is_platform_account,
            version,
            product_type,
            merchant_account_type,
        }
    }
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug, serde::Serialize)]
pub struct MerchantAccount {
    id: common_utils::id_type::MerchantId,
    pub merchant_name: OptionalEncryptableName,
    pub merchant_details: OptionalEncryptableValue,
    pub publishable_key: String,
    pub storage_scheme: MerchantStorageScheme,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub created_at: time::PrimitiveDateTime,
    pub modified_at: time::PrimitiveDateTime,
    pub organization_id: common_utils::id_type::OrganizationId,
    pub recon_status: diesel_models::enums::ReconStatus,
    pub is_platform_account: bool,
    pub version: common_enums::ApiVersion,
    pub product_type: Option<common_enums::MerchantProductType>,
    pub merchant_account_type: common_enums::MerchantAccountType,
}

impl MerchantAccount {
    #[cfg(feature = "v1")]
    /// Get the unique identifier of MerchantAccount
    pub fn get_id(&self) -> &common_utils::id_type::MerchantId {
        &self.merchant_id
    }

    #[cfg(feature = "v2")]
    /// Get the unique identifier of MerchantAccount
    pub fn get_id(&self) -> &common_utils::id_type::MerchantId {
        &self.id
    }

    /// Get the organization_id from MerchantAccount
    pub fn get_org_id(&self) -> &common_utils::id_type::OrganizationId {
        &self.organization_id
    }

    /// Get the merchant_details from MerchantAccount
    pub fn get_merchant_details(&self) -> &OptionalEncryptableValue {
        &self.merchant_details
    }

    /// Extract merchant_tax_registration_id from merchant_details
    pub fn get_merchant_tax_registration_id(&self) -> Option<Secret<String>> {
        self.merchant_details.as_ref().and_then(|details| {
            details
                .get_inner()
                .peek()
                .get("merchant_tax_registration_id")
                .and_then(|id| id.as_str().map(|s| Secret::new(s.to_string())))
        })
    }

    /// Check whether the merchant account is a platform account
    pub fn is_platform_account(&self) -> bool {
        matches!(
            self.merchant_account_type,
            common_enums::MerchantAccountType::Platform
        )
    }
}

#[cfg(feature = "v1")]
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum MerchantAccountUpdate {
    Update {
        merchant_name: OptionalEncryptableName,
        merchant_details: OptionalEncryptableValue,
        return_url: Option<String>,
        webhook_details: Option<diesel_models::business_profile::WebhookDetails>,
        sub_merchants_enabled: Option<bool>,
        parent_merchant_id: Option<common_utils::id_type::MerchantId>,
        enable_payment_response_hash: Option<bool>,
        payment_response_hash_key: Option<String>,
        redirect_to_merchant_with_http_post: Option<bool>,
        publishable_key: Option<String>,
        locker_id: Option<String>,
        metadata: Option<pii::SecretSerdeValue>,
        routing_algorithm: Option<serde_json::Value>,
        primary_business_details: Option<serde_json::Value>,
        intent_fulfillment_time: Option<i64>,
        frm_routing_algorithm: Option<serde_json::Value>,
        payout_routing_algorithm: Option<serde_json::Value>,
        default_profile: Option<Option<common_utils::id_type::ProfileId>>,
        payment_link_config: Option<serde_json::Value>,
        pm_collect_link_config: Option<serde_json::Value>,
    },
    StorageSchemeUpdate {
        storage_scheme: MerchantStorageScheme,
    },
    ReconUpdate {
        recon_status: diesel_models::enums::ReconStatus,
    },
    UnsetDefaultProfile,
    ModifiedAtUpdate,
    ToPlatformAccount,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone)]
pub enum MerchantAccountUpdate {
    Update {
        merchant_name: OptionalEncryptableName,
        merchant_details: OptionalEncryptableValue,
        publishable_key: Option<String>,
        metadata: Option<Box<pii::SecretSerdeValue>>,
    },
    StorageSchemeUpdate {
        storage_scheme: MerchantStorageScheme,
    },
    ReconUpdate {
        recon_status: diesel_models::enums::ReconStatus,
    },
    ModifiedAtUpdate,
    ToPlatformAccount,
}

#[cfg(feature = "v1")]
impl From<MerchantAccountUpdate> for MerchantAccountUpdateInternal {
    fn from(merchant_account_update: MerchantAccountUpdate) -> Self {
        let now = date_time::now();

        match merchant_account_update {
            MerchantAccountUpdate::Update {
                merchant_name,
                merchant_details,
                webhook_details,
                return_url,
                routing_algorithm,
                sub_merchants_enabled,
                parent_merchant_id,
                enable_payment_response_hash,
                payment_response_hash_key,
                redirect_to_merchant_with_http_post,
                publishable_key,
                locker_id,
                metadata,
                primary_business_details,
                intent_fulfillment_time,
                frm_routing_algorithm,
                payout_routing_algorithm,
                default_profile,
                payment_link_config,
                pm_collect_link_config,
            } => Self {
                merchant_name: merchant_name.map(Encryption::from),
                merchant_details: merchant_details.map(Encryption::from),
                frm_routing_algorithm,
                webhook_details,
                routing_algorithm,
                sub_merchants_enabled,
                parent_merchant_id,
                return_url,
                enable_payment_response_hash,
                payment_response_hash_key,
                redirect_to_merchant_with_http_post,
                publishable_key,
                locker_id,
                metadata,
                primary_business_details,
                modified_at: now,
                intent_fulfillment_time,
                payout_routing_algorithm,
                default_profile,
                payment_link_config,
                pm_collect_link_config,
                storage_scheme: None,
                organization_id: None,
                is_recon_enabled: None,
                recon_status: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self {
                storage_scheme: Some(storage_scheme),
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                return_url: None,
                webhook_details: None,
                sub_merchants_enabled: None,
                parent_merchant_id: None,
                enable_payment_response_hash: None,
                payment_response_hash_key: None,
                redirect_to_merchant_with_http_post: None,
                publishable_key: None,
                locker_id: None,
                metadata: None,
                routing_algorithm: None,
                primary_business_details: None,
                intent_fulfillment_time: None,
                frm_routing_algorithm: None,
                payout_routing_algorithm: None,
                organization_id: None,
                is_recon_enabled: None,
                default_profile: None,
                recon_status: None,
                payment_link_config: None,
                pm_collect_link_config: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::ReconUpdate { recon_status } => Self {
                recon_status: Some(recon_status),
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                return_url: None,
                webhook_details: None,
                sub_merchants_enabled: None,
                parent_merchant_id: None,
                enable_payment_response_hash: None,
                payment_response_hash_key: None,
                redirect_to_merchant_with_http_post: None,
                publishable_key: None,
                storage_scheme: None,
                locker_id: None,
                metadata: None,
                routing_algorithm: None,
                primary_business_details: None,
                intent_fulfillment_time: None,
                frm_routing_algorithm: None,
                payout_routing_algorithm: None,
                organization_id: None,
                is_recon_enabled: None,
                default_profile: None,
                payment_link_config: None,
                pm_collect_link_config: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::UnsetDefaultProfile => Self {
                default_profile: Some(None),
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                return_url: None,
                webhook_details: None,
                sub_merchants_enabled: None,
                parent_merchant_id: None,
                enable_payment_response_hash: None,
                payment_response_hash_key: None,
                redirect_to_merchant_with_http_post: None,
                publishable_key: None,
                storage_scheme: None,
                locker_id: None,
                metadata: None,
                routing_algorithm: None,
                primary_business_details: None,
                intent_fulfillment_time: None,
                frm_routing_algorithm: None,
                payout_routing_algorithm: None,
                organization_id: None,
                is_recon_enabled: None,
                recon_status: None,
                payment_link_config: None,
                pm_collect_link_config: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::ModifiedAtUpdate => Self {
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                return_url: None,
                webhook_details: None,
                sub_merchants_enabled: None,
                parent_merchant_id: None,
                enable_payment_response_hash: None,
                payment_response_hash_key: None,
                redirect_to_merchant_with_http_post: None,
                publishable_key: None,
                storage_scheme: None,
                locker_id: None,
                metadata: None,
                routing_algorithm: None,
                primary_business_details: None,
                intent_fulfillment_time: None,
                frm_routing_algorithm: None,
                payout_routing_algorithm: None,
                organization_id: None,
                is_recon_enabled: None,
                default_profile: None,
                recon_status: None,
                payment_link_config: None,
                pm_collect_link_config: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::ToPlatformAccount => Self {
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                return_url: None,
                webhook_details: None,
                sub_merchants_enabled: None,
                parent_merchant_id: None,
                enable_payment_response_hash: None,
                payment_response_hash_key: None,
                redirect_to_merchant_with_http_post: None,
                publishable_key: None,
                storage_scheme: None,
                locker_id: None,
                metadata: None,
                routing_algorithm: None,
                primary_business_details: None,
                intent_fulfillment_time: None,
                frm_routing_algorithm: None,
                payout_routing_algorithm: None,
                organization_id: None,
                is_recon_enabled: None,
                default_profile: None,
                recon_status: None,
                payment_link_config: None,
                pm_collect_link_config: None,
                is_platform_account: Some(true),
                product_type: None,
            },
        }
    }
}

#[cfg(feature = "v2")]
impl From<MerchantAccountUpdate> for MerchantAccountUpdateInternal {
    fn from(merchant_account_update: MerchantAccountUpdate) -> Self {
        let now = date_time::now();

        match merchant_account_update {
            MerchantAccountUpdate::Update {
                merchant_name,
                merchant_details,
                publishable_key,
                metadata,
            } => Self {
                merchant_name: merchant_name.map(Encryption::from),
                merchant_details: merchant_details.map(Encryption::from),
                publishable_key,
                metadata: metadata.map(|metadata| *metadata),
                modified_at: now,
                storage_scheme: None,
                organization_id: None,
                recon_status: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self {
                storage_scheme: Some(storage_scheme),
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                publishable_key: None,
                metadata: None,
                organization_id: None,
                recon_status: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::ReconUpdate { recon_status } => Self {
                recon_status: Some(recon_status),
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                publishable_key: None,
                storage_scheme: None,
                metadata: None,
                organization_id: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::ModifiedAtUpdate => Self {
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                publishable_key: None,
                storage_scheme: None,
                metadata: None,
                organization_id: None,
                recon_status: None,
                is_platform_account: None,
                product_type: None,
            },
            MerchantAccountUpdate::ToPlatformAccount => Self {
                modified_at: now,
                merchant_name: None,
                merchant_details: None,
                publishable_key: None,
                storage_scheme: None,
                metadata: None,
                organization_id: None,
                recon_status: None,
                is_platform_account: Some(true),
                product_type: None,
            },
        }
    }
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl super::behaviour::Conversion for MerchantAccount {
    type DstType = diesel_models::merchant_account::MerchantAccount;
    type NewDstType = diesel_models::merchant_account::MerchantAccountNew;
    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        let id = self.get_id().to_owned();

        let setter = diesel_models::merchant_account::MerchantAccountSetter {
            id,
            merchant_name: self.merchant_name.map(|name| name.into()),
            merchant_details: self.merchant_details.map(|details| details.into()),
            publishable_key: Some(self.publishable_key),
            storage_scheme: self.storage_scheme,
            metadata: self.metadata,
            created_at: self.created_at,
            modified_at: self.modified_at,
            organization_id: self.organization_id,
            recon_status: self.recon_status,
            version: common_types::consts::API_VERSION,
            is_platform_account: self.is_platform_account,
            product_type: self.product_type,
            merchant_account_type: self.merchant_account_type,
        };

        Ok(diesel_models::MerchantAccount::from(setter))
    }

    async fn convert_back(
        state: &keymanager::KeyManagerState,
        item: Self::DstType,
        key: &Secret<Vec<u8>>,
        key_manager_identifier: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        let id = item.get_id().to_owned();
        let publishable_key =
            item.publishable_key
                .ok_or(ValidationError::MissingRequiredField {
                    field_name: "publishable_key".to_string(),
                })?;

        async {
            Ok::<Self, error_stack::Report<common_utils::errors::CryptoError>>(Self {
                id,
                merchant_name: item
                    .merchant_name
                    .async_lift(|inner| async {
                        crypto_operation(
                            state,
                            type_name!(Self::DstType),
                            CryptoOperation::DecryptOptional(inner),
                            key_manager_identifier.clone(),
                            key.peek(),
                        )
                        .await
                        .and_then(|val| val.try_into_optionaloperation())
                    })
                    .await?,
                merchant_details: item
                    .merchant_details
                    .async_lift(|inner| async {
                        crypto_operation(
                            state,
                            type_name!(Self::DstType),
                            CryptoOperation::DecryptOptional(inner),
                            key_manager_identifier.clone(),
                            key.peek(),
                        )
                        .await
                        .and_then(|val| val.try_into_optionaloperation())
                    })
                    .await?,
                publishable_key,
                storage_scheme: item.storage_scheme,
                metadata: item.metadata,
                created_at: item.created_at,
                modified_at: item.modified_at,
                organization_id: item.organization_id,
                recon_status: item.recon_status,
                is_platform_account: item.is_platform_account,
                version: item.version,
                product_type: item.product_type,
                merchant_account_type: item.merchant_account_type.unwrap_or_default(),
            })
        }
        .await
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting merchant data".to_string(),
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let now = date_time::now();
        Ok(diesel_models::merchant_account::MerchantAccountNew {
            id: self.id,
            merchant_name: self.merchant_name.map(Encryption::from),
            merchant_details: self.merchant_details.map(Encryption::from),
            publishable_key: Some(self.publishable_key),
            metadata: self.metadata,
            created_at: now,
            modified_at: now,
            organization_id: self.organization_id,
            recon_status: self.recon_status,
            version: common_types::consts::API_VERSION,
            is_platform_account: self.is_platform_account,
            product_type: self
                .product_type
                .or(Some(common_enums::MerchantProductType::Orchestration)),
            merchant_account_type: self.merchant_account_type,
        })
    }
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl super::behaviour::Conversion for MerchantAccount {
    type DstType = diesel_models::merchant_account::MerchantAccount;
    type NewDstType = diesel_models::merchant_account::MerchantAccountNew;
    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        let setter = diesel_models::merchant_account::MerchantAccountSetter {
            merchant_id: self.merchant_id,
            return_url: self.return_url,
            enable_payment_response_hash: self.enable_payment_response_hash,
            payment_response_hash_key: self.payment_response_hash_key,
            redirect_to_merchant_with_http_post: self.redirect_to_merchant_with_http_post,
            merchant_name: self.merchant_name.map(|name| name.into()),
            merchant_details: self.merchant_details.map(|details| details.into()),
            webhook_details: self.webhook_details,
            sub_merchants_enabled: self.sub_merchants_enabled,
            parent_merchant_id: self.parent_merchant_id,
            publishable_key: Some(self.publishable_key),
            storage_scheme: self.storage_scheme,
            locker_id: self.locker_id,
            metadata: self.metadata,
            routing_algorithm: self.routing_algorithm,
            primary_business_details: self.primary_business_details,
            created_at: self.created_at,
            modified_at: self.modified_at,
            intent_fulfillment_time: self.intent_fulfillment_time,
            frm_routing_algorithm: self.frm_routing_algorithm,
            payout_routing_algorithm: self.payout_routing_algorithm,
            organization_id: self.organization_id,
            is_recon_enabled: self.is_recon_enabled,
            default_profile: self.default_profile,
            recon_status: self.recon_status,
            payment_link_config: self.payment_link_config,
            pm_collect_link_config: self.pm_collect_link_config,
            version: self.version,
            is_platform_account: self.is_platform_account,
            product_type: self.product_type,
            merchant_account_type: self.merchant_account_type,
        };

        Ok(diesel_models::MerchantAccount::from(setter))
    }

    async fn convert_back(
        state: &keymanager::KeyManagerState,
        item: Self::DstType,
        key: &Secret<Vec<u8>>,
        key_manager_identifier: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        let merchant_id = item.get_id().to_owned();
        let publishable_key =
            item.publishable_key
                .ok_or(ValidationError::MissingRequiredField {
                    field_name: "publishable_key".to_string(),
                })?;

        async {
            Ok::<Self, error_stack::Report<common_utils::errors::CryptoError>>(Self {
                merchant_id,
                return_url: item.return_url,
                enable_payment_response_hash: item.enable_payment_response_hash,
                payment_response_hash_key: item.payment_response_hash_key,
                redirect_to_merchant_with_http_post: item.redirect_to_merchant_with_http_post,
                merchant_name: item
                    .merchant_name
                    .async_lift(|inner| async {
                        crypto_operation(
                            state,
                            type_name!(Self::DstType),
                            CryptoOperation::DecryptOptional(inner),
                            key_manager_identifier.clone(),
                            key.peek(),
                        )
                        .await
                        .and_then(|val| val.try_into_optionaloperation())
                    })
                    .await?,
                merchant_details: item
                    .merchant_details
                    .async_lift(|inner| async {
                        crypto_operation(
                            state,
                            type_name!(Self::DstType),
                            CryptoOperation::DecryptOptional(inner),
                            key_manager_identifier.clone(),
                            key.peek(),
                        )
                        .await
                        .and_then(|val| val.try_into_optionaloperation())
                    })
                    .await?,
                webhook_details: item.webhook_details,
                sub_merchants_enabled: item.sub_merchants_enabled,
                parent_merchant_id: item.parent_merchant_id,
                publishable_key,
                storage_scheme: item.storage_scheme,
                locker_id: item.locker_id,
                metadata: item.metadata,
                routing_algorithm: item.routing_algorithm,
                frm_routing_algorithm: item.frm_routing_algorithm,
                primary_business_details: item.primary_business_details,
                created_at: item.created_at,
                modified_at: item.modified_at,
                intent_fulfillment_time: item.intent_fulfillment_time,
                payout_routing_algorithm: item.payout_routing_algorithm,
                organization_id: item.organization_id,
                is_recon_enabled: item.is_recon_enabled,
                default_profile: item.default_profile,
                recon_status: item.recon_status,
                payment_link_config: item.payment_link_config,
                pm_collect_link_config: item.pm_collect_link_config,
                version: item.version,
                is_platform_account: item.is_platform_account,
                product_type: item.product_type,
                merchant_account_type: item.merchant_account_type.unwrap_or_default(),
            })
        }
        .await
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting merchant data".to_string(),
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let now = date_time::now();
        Ok(diesel_models::merchant_account::MerchantAccountNew {
            id: Some(self.merchant_id.clone()),
            merchant_id: self.merchant_id,
            merchant_name: self.merchant_name.map(Encryption::from),
            merchant_details: self.merchant_details.map(Encryption::from),
            return_url: self.return_url,
            webhook_details: self.webhook_details,
            sub_merchants_enabled: self.sub_merchants_enabled,
            parent_merchant_id: self.parent_merchant_id,
            enable_payment_response_hash: Some(self.enable_payment_response_hash),
            payment_response_hash_key: self.payment_response_hash_key,
            redirect_to_merchant_with_http_post: Some(self.redirect_to_merchant_with_http_post),
            publishable_key: Some(self.publishable_key),
            locker_id: self.locker_id,
            metadata: self.metadata,
            routing_algorithm: self.routing_algorithm,
            primary_business_details: self.primary_business_details,
            created_at: now,
            modified_at: now,
            intent_fulfillment_time: self.intent_fulfillment_time,
            frm_routing_algorithm: self.frm_routing_algorithm,
            payout_routing_algorithm: self.payout_routing_algorithm,
            organization_id: self.organization_id,
            is_recon_enabled: self.is_recon_enabled,
            default_profile: self.default_profile,
            recon_status: self.recon_status,
            payment_link_config: self.payment_link_config,
            pm_collect_link_config: self.pm_collect_link_config,
            version: common_types::consts::API_VERSION,
            is_platform_account: self.is_platform_account,
            product_type: self
                .product_type
                .or(Some(common_enums::MerchantProductType::Orchestration)),
            merchant_account_type: self.merchant_account_type,
        })
    }
}

impl MerchantAccount {
    pub fn get_compatible_connector(&self) -> Option<api_models::enums::Connector> {
        let metadata: Option<api_models::admin::MerchantAccountMetadata> =
            self.metadata.as_ref().and_then(|meta| {
                meta.clone()
                    .parse_value("MerchantAccountMetadata")
                    .map_err(|err| logger::error!("Failed to deserialize {:?}", err))
                    .ok()
            });
        metadata.and_then(|a| a.compatible_connector)
    }
}