diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs index dc39059c135..b0d4535ddb7 100644 --- a/crates/diesel_models/src/customers.rs +++ b/crates/diesel_models/src/customers.rs @@ -64,13 +64,7 @@ impl From for Customer { #[cfg(all(feature = "v2", feature = "customer_v2"))] #[derive( - Clone, - Debug, - PartialEq, - Insertable, - router_derive::DebugAsDisplay, - serde::Deserialize, - serde::Serialize, + Clone, Debug, Insertable, router_derive::DebugAsDisplay, serde::Deserialize, serde::Serialize, )] #[diesel(table_name = customers, primary_key(id))] pub struct CustomerNew { @@ -82,7 +76,7 @@ pub struct CustomerNew { pub description: Option, pub created_at: PrimitiveDateTime, pub metadata: Option, - pub connector_customer: Option, + pub connector_customer: Option, pub modified_at: PrimitiveDateTime, pub default_payment_method_id: Option, pub updated_by: Option, @@ -164,7 +158,7 @@ pub struct Customer { pub description: Option, pub created_at: PrimitiveDateTime, pub metadata: Option, - pub connector_customer: Option, + pub connector_customer: Option, pub modified_at: PrimitiveDateTime, pub default_payment_method_id: Option, pub updated_by: Option, @@ -242,7 +236,7 @@ pub struct CustomerUpdateInternal { pub phone_country_code: Option, pub metadata: Option, pub modified_at: PrimitiveDateTime, - pub connector_customer: Option, + pub connector_customer: Option, pub default_payment_method_id: Option>, pub updated_by: Option, pub default_billing_address: Option, @@ -289,3 +283,31 @@ impl CustomerUpdateInternal { } } } + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, diesel::AsExpression)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +#[serde(transparent)] +pub struct ConnectorCustomerMap( + std::collections::HashMap, +); + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +common_utils::impl_to_sql_from_sql_json!(ConnectorCustomerMap); + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +impl std::ops::Deref for ConnectorCustomerMap { + type Target = + std::collections::HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +impl std::ops::DerefMut for ConnectorCustomerMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 3e49a596d88..b2385efb6d2 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -985,7 +985,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled: None, max_auto_retries_enabled: None, - is_click_to_pay_enabled: None, + is_click_to_pay_enabled, authentication_product_ids, three_ds_decision_manager_config, } diff --git a/crates/hyperswitch_domain_models/src/customer.rs b/crates/hyperswitch_domain_models/src/customer.rs index 58103c08faa..1497c9e3622 100644 --- a/crates/hyperswitch_domain_models/src/customer.rs +++ b/crates/hyperswitch_domain_models/src/customer.rs @@ -56,7 +56,7 @@ pub struct Customer { pub description: Option, pub created_at: PrimitiveDateTime, pub metadata: Option, - pub connector_customer: Option, + pub connector_customer: Option, pub modified_at: PrimitiveDateTime, pub default_payment_method_id: Option, pub updated_by: Option, @@ -80,6 +80,31 @@ impl Customer { pub fn get_id(&self) -> &id_type::GlobalCustomerId { &self.id } + + /// Get the connector customer ID for the specified connector label, if present + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] + pub fn get_connector_customer_id(&self, connector_label: &str) -> Option<&str> { + use masking::PeekInterface; + + self.connector_customer + .as_ref() + .and_then(|connector_customer_value| { + connector_customer_value.peek().get(connector_label) + }) + .and_then(|connector_customer| connector_customer.as_str()) + } + + /// Get the connector customer ID for the specified merchant connector account ID, if present + #[cfg(all(feature = "v2", feature = "customer_v2"))] + pub fn get_connector_customer_id( + &self, + merchant_connector_id: &id_type::MerchantConnectorAccountId, + ) -> Option<&str> { + self.connector_customer + .as_ref() + .and_then(|connector_customer_map| connector_customer_map.get(merchant_connector_id)) + .map(|connector_customer_id| connector_customer_id.as_str()) + } } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] @@ -300,24 +325,28 @@ impl super::behaviour::Conversion for Customer { } } +#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[derive(Clone, Debug)] +pub struct CustomerGeneralUpdate { + pub name: crypto::OptionalEncryptableName, + pub email: Box, + pub phone: Box, + pub description: Option, + pub phone_country_code: Option, + pub metadata: Option, + pub connector_customer: Box>, + pub default_billing_address: Option, + pub default_shipping_address: Option, + pub default_payment_method_id: Option>, + pub status: Option, +} + #[cfg(all(feature = "v2", feature = "customer_v2"))] #[derive(Clone, Debug)] pub enum CustomerUpdate { - Update { - name: crypto::OptionalEncryptableName, - email: Box, - phone: Box, - description: Option, - phone_country_code: Option, - metadata: Option, - connector_customer: Box>, - default_billing_address: Option, - default_shipping_address: Option, - default_payment_method_id: Option>, - status: Option, - }, + Update(Box), ConnectorCustomer { - connector_customer: Option, + connector_customer: Option, }, UpdateDefaultPaymentMethod { default_payment_method_id: Option>, @@ -328,33 +357,36 @@ pub enum CustomerUpdate { impl From for CustomerUpdateInternal { fn from(customer_update: CustomerUpdate) -> Self { match customer_update { - CustomerUpdate::Update { - name, - email, - phone, - description, - phone_country_code, - metadata, - connector_customer, - default_billing_address, - default_shipping_address, - default_payment_method_id, - status, - } => Self { - name: name.map(Encryption::from), - email: email.map(Encryption::from), - phone: phone.map(Encryption::from), - description, - phone_country_code, - metadata, - connector_customer: *connector_customer, - modified_at: date_time::now(), - default_billing_address, - default_shipping_address, - default_payment_method_id, - updated_by: None, - status, - }, + CustomerUpdate::Update(update) => { + let CustomerGeneralUpdate { + name, + email, + phone, + description, + phone_country_code, + metadata, + connector_customer, + default_billing_address, + default_shipping_address, + default_payment_method_id, + status, + } = *update; + Self { + name: name.map(Encryption::from), + email: email.map(Encryption::from), + phone: phone.map(Encryption::from), + description, + phone_country_code, + metadata, + connector_customer: *connector_customer, + modified_at: date_time::now(), + default_billing_address, + default_shipping_address, + default_payment_method_id, + updated_by: None, + status, + } + } CustomerUpdate::ConnectorCustomer { connector_customer } => Self { connector_customer, name: None, diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index dabc9d37086..353c980c769 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -679,19 +679,20 @@ impl CustomerDeleteBridge for id_type::GlobalCustomerId { redacted_encrypted_value.clone().into_encrypted(), ); - let updated_customer = storage::CustomerUpdate::Update { - name: Some(redacted_encrypted_value.clone()), - email: Box::new(Some(redacted_encrypted_email)), - phone: Box::new(Some(redacted_encrypted_value.clone())), - description: Some(Description::from_str_unchecked(REDACTED)), - phone_country_code: Some(REDACTED.to_string()), - metadata: None, - connector_customer: Box::new(None), - default_billing_address: None, - default_shipping_address: None, - default_payment_method_id: None, - status: Some(common_enums::DeleteStatus::Redacted), - }; + let updated_customer = + storage::CustomerUpdate::Update(Box::new(storage::CustomerGeneralUpdate { + name: Some(redacted_encrypted_value.clone()), + email: Box::new(Some(redacted_encrypted_email)), + phone: Box::new(Some(redacted_encrypted_value.clone())), + description: Some(Description::from_str_unchecked(REDACTED)), + phone_country_code: Some(REDACTED.to_string()), + metadata: None, + connector_customer: Box::new(None), + default_billing_address: None, + default_shipping_address: None, + default_payment_method_id: None, + status: Some(common_enums::DeleteStatus::Redacted), + })); db.update_customer_by_global_id( key_manager_state, @@ -1338,7 +1339,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { &domain_customer.id, domain_customer.to_owned(), merchant_account.get_id(), - storage::CustomerUpdate::Update { + storage::CustomerUpdate::Update(Box::new(storage::CustomerGeneralUpdate { name: encryptable_customer.name, email: Box::new(encryptable_customer.email.map(|email| { let encryptable: Encryptable> = @@ -1357,7 +1358,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { default_shipping_address: encrypted_customer_shipping_address.map(Into::into), default_payment_method_id: Some(self.default_payment_method_id.clone()), status: None, - }, + })), key_store, merchant_account.storage_scheme, ) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 6bee4263b9b..57e72a42941 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -3016,6 +3016,16 @@ where id: merchant_connector_id.get_string_repr().to_owned(), })?; + let updated_customer = call_create_connector_customer_if_required( + state, + customer, + merchant_account, + key_store, + &merchant_connector_account, + payment_data, + ) + .await?; + let mut router_data = payment_data .construct_router_data( state, @@ -3068,8 +3078,7 @@ where payment_data.clone(), customer.clone(), merchant_account.storage_scheme, - // TODO: update the customer with connector customer id - None, + updated_customer, key_store, frm_suggestion, header_payload.clone(), @@ -3894,7 +3903,6 @@ where merchant_connector_account.get_mca_id(), )?; - #[cfg(feature = "v1")] let label = { let connector_label = core_utils::get_connector_label( payment_data.get_payment_intent().business_country, @@ -3925,15 +3933,6 @@ where } }; - #[cfg(feature = "v2")] - let label = { - merchant_connector_account - .get_mca_id() - .get_required_value("merchant_connector_account_id")? - .get_string_repr() - .to_owned() - }; - let (should_call_connector, existing_connector_customer_id) = customers::should_call_connector_create_customer( state, &connector, customer, &label, @@ -3961,7 +3960,90 @@ where let customer_update = customers::update_connector_customer_in_customers( &label, customer.as_ref(), - &connector_customer_id, + connector_customer_id.clone(), + ) + .await; + + payment_data.set_connector_customer_id(connector_customer_id); + Ok(customer_update) + } else { + // Customer already created in previous calls use the same value, no need to update + payment_data.set_connector_customer_id( + existing_connector_customer_id.map(ToOwned::to_owned), + ); + Ok(None) + } + } + None => Ok(None), + } +} + +#[cfg(feature = "v2")] +pub async fn call_create_connector_customer_if_required( + state: &SessionState, + customer: &Option, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + merchant_connector_account: &domain::MerchantConnectorAccount, + payment_data: &mut D, +) -> RouterResult> +where + F: Send + Clone + Sync, + Req: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let connector_name = payment_data.get_payment_attempt().connector.clone(); + + match connector_name { + Some(connector_name) => { + let connector = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &connector_name, + api::GetToken::Connector, + Some(merchant_connector_account.get_id()), + )?; + + let merchant_connector_id = merchant_connector_account.get_id(); + + let (should_call_connector, existing_connector_customer_id) = + customers::should_call_connector_create_customer( + state, + &connector, + customer, + &merchant_connector_id, + ); + + if should_call_connector { + // Create customer at connector and update the customer table to store this data + let router_data = payment_data + .construct_router_data( + state, + connector.connector.id(), + merchant_account, + key_store, + customer, + merchant_connector_account, + None, + None, + ) + .await?; + + let connector_customer_id = router_data + .create_connector_customer(state, &connector) + .await?; + + let customer_update = customers::update_connector_customer_in_customers( + merchant_connector_id, + customer.as_ref(), + connector_customer_id.clone(), ) .await; diff --git a/crates/router/src/core/payments/customers.rs b/crates/router/src/core/payments/customers.rs index b11216c423d..83f01349787 100644 --- a/crates/router/src/core/payments/customers.rs +++ b/crates/router/src/core/payments/customers.rs @@ -1,5 +1,5 @@ use common_utils::pii; -use masking::{ExposeOptionInterface, PeekInterface}; +use masking::ExposeOptionInterface; use router_env::{instrument, tracing}; use crate::{ @@ -73,22 +73,37 @@ pub async fn create_connector_customer( Ok(connector_customer_id) } -pub fn get_connector_customer_details_if_present<'a>( - customer: &'a domain::Customer, - connector_name: &str, -) -> Option<&'a str> { - customer +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +pub fn should_call_connector_create_customer<'a>( + state: &SessionState, + connector: &api::ConnectorData, + customer: &'a Option, + connector_label: &str, +) -> (bool, Option<&'a str>) { + // Check if create customer is required for the connector + let connector_needs_customer = state + .conf .connector_customer - .as_ref() - .and_then(|connector_customer_value| connector_customer_value.peek().get(connector_name)) - .and_then(|connector_customer| connector_customer.as_str()) + .connector_list + .contains(&connector.connector_name); + + if connector_needs_customer { + let connector_customer_details = customer + .as_ref() + .and_then(|customer| customer.get_connector_customer_id(connector_label)); + let should_call_connector = connector_customer_details.is_none(); + (should_call_connector, connector_customer_details) + } else { + (false, None) + } } +#[cfg(all(feature = "v2", feature = "customer_v2"))] pub fn should_call_connector_create_customer<'a>( state: &SessionState, connector: &api::ConnectorData, customer: &'a Option, - connector_label: &str, + merchant_connector_id: &common_utils::id_type::MerchantConnectorAccountId, ) -> (bool, Option<&'a str>) { // Check if create customer is required for the connector let connector_needs_customer = state @@ -98,9 +113,9 @@ pub fn should_call_connector_create_customer<'a>( .contains(&connector.connector_name); if connector_needs_customer { - let connector_customer_details = customer.as_ref().and_then(|customer| { - get_connector_customer_details_if_present(customer, connector_label) - }); + let connector_customer_details = customer + .as_ref() + .and_then(|customer| customer.get_connector_customer_id(merchant_connector_id)); let should_call_connector = connector_customer_details.is_none(); (should_call_connector, connector_customer_details) } else { @@ -108,25 +123,23 @@ pub fn should_call_connector_create_customer<'a>( } } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[instrument] pub async fn update_connector_customer_in_customers( connector_label: &str, customer: Option<&domain::Customer>, - connector_customer_id: &Option, + connector_customer_id: Option, ) -> Option { - let connector_customer_map = customer + let mut connector_customer_map = customer .and_then(|customer| customer.connector_customer.clone().expose_option()) .and_then(|connector_customer| connector_customer.as_object().cloned()) .unwrap_or_default(); - let updated_connector_customer_map = - connector_customer_id.as_ref().map(|connector_customer_id| { - let mut connector_customer_map = connector_customer_map; - let connector_customer_value = - serde_json::Value::String(connector_customer_id.to_string()); - connector_customer_map.insert(connector_label.to_string(), connector_customer_value); - connector_customer_map - }); + let updated_connector_customer_map = connector_customer_id.map(|connector_customer_id| { + let connector_customer_value = serde_json::Value::String(connector_customer_id); + connector_customer_map.insert(connector_label.to_string(), connector_customer_value); + connector_customer_map + }); updated_connector_customer_map .map(serde_json::Value::Object) @@ -136,3 +149,22 @@ pub async fn update_connector_customer_in_customers( }, ) } + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[instrument] +pub async fn update_connector_customer_in_customers( + merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, + customer: Option<&domain::Customer>, + connector_customer_id: Option, +) -> Option { + connector_customer_id.map(|connector_customer_id| { + let mut connector_customer_map = customer + .and_then(|customer| customer.connector_customer.clone()) + .unwrap_or_default(); + connector_customer_map.insert(merchant_connector_id, connector_customer_id); + + storage::CustomerUpdate::ConnectorCustomer { + connector_customer: Some(connector_customer_map), + } + }) +} diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index 2e4c0190adb..79ebc2d8a38 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -462,6 +462,25 @@ impl UpdateTracker, PaymentsConfirmInt payment_data.payment_attempt = updated_payment_attempt; + if let Some((customer, updated_customer)) = customer.zip(updated_customer) { + let customer_id = customer.get_id().clone(); + let customer_merchant_id = customer.merchant_id.clone(); + + let _updated_customer = db + .update_customer_by_global_id( + key_manager_state, + &customer_id, + customer, + &customer_merchant_id, + updated_customer, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update customer during `update_trackers`")?; + } + Ok((Box::new(self), payment_data)) } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 81393e792b0..7302831e509 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -211,6 +211,12 @@ pub async fn construct_payment_router_data_for_authorize<'a>( "Invalid global customer generated, not able to convert to reference id", )?; + let connector_customer_id = customer.as_ref().and_then(|customer| { + customer + .get_connector_customer_id(&merchant_connector_account.get_id()) + .map(String::from) + }); + let payment_method = payment_data.payment_attempt.payment_method_type; let router_base_url = &state.base_url; @@ -352,7 +358,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( reference_id: None, payment_method_status: None, payment_method_token: None, - connector_customer: None, + connector_customer: connector_customer_id, recurring_mandate_payment_data: None, // TODO: This has to be generated as the reference id based on the connector configuration // Some connectros might not accept accept the global id. This has to be done when generating the reference id @@ -867,6 +873,12 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>( "Invalid global customer generated, not able to convert to reference id", )?; + let connector_customer_id = customer.as_ref().and_then(|customer| { + customer + .get_connector_customer_id(&merchant_connector_account.get_id()) + .map(String::from) + }); + let payment_method = payment_data.payment_attempt.payment_method_type; let router_base_url = &state.base_url; @@ -994,7 +1006,7 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>( reference_id: None, payment_method_status: None, payment_method_token: None, - connector_customer: None, + connector_customer: connector_customer_id, recurring_mandate_payment_data: None, // TODO: This has to be generated as the reference id based on the connector configuration // Some connectros might not accept accept the global id. This has to be done when generating the reference id diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index 58d5e22b4b9..392cfd4382f 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -1203,6 +1203,7 @@ pub async fn complete_create_recipient( Ok(()) } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] pub async fn create_recipient( state: &SessionState, merchant_account: &domain::MerchantAccount, @@ -1262,7 +1263,7 @@ pub async fn create_recipient( customers::update_connector_customer_in_customers( &connector_label, Some(&customer), - &recipient_create_data.connector_payout_id.clone(), + recipient_create_data.connector_payout_id.clone(), ) .await { @@ -1402,6 +1403,17 @@ pub async fn create_recipient( Ok(()) } +#[cfg(all(feature = "v2", feature = "customer_v2"))] +pub async fn create_recipient( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + connector_data: &api::ConnectorData, + payout_data: &mut PayoutData, +) -> RouterResult<()> { + todo!() +} + pub async fn complete_payout_eligibility( state: &SessionState, merchant_account: &domain::MerchantAccount, diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 77cd9c266ff..7eb7925017e 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -29,10 +29,7 @@ use crate::{ transformers::{DataDuplicationCheck, StoreCardReq, StoreGenericReq, StoreLockerReq}, vault, }, - payments::{ - customers::get_connector_customer_details_if_present, helpers as payment_helpers, - routing, CustomerDetails, - }, + payments::{helpers as payment_helpers, routing, CustomerDetails}, routing::TransactionData, utils as core_utils, }, @@ -965,6 +962,7 @@ pub async fn get_default_payout_connector( )) } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] pub fn should_call_payout_connector_create_customer<'a>( state: &'a SessionState, connector: &'a api::ConnectorData, @@ -981,9 +979,39 @@ pub fn should_call_payout_connector_create_customer<'a>( .contains(&connector); if connector_needs_customer { - let connector_customer_details = customer.as_ref().and_then(|customer| { - get_connector_customer_details_if_present(customer, connector_label) - }); + let connector_customer_details = customer + .as_ref() + .and_then(|customer| customer.get_connector_customer_id(connector_label)); + let should_call_connector = connector_customer_details.is_none(); + (should_call_connector, connector_customer_details) + } else { + (false, None) + } + } + _ => (false, None), + } +} + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +pub fn should_call_payout_connector_create_customer<'a>( + state: &'a SessionState, + connector: &'a api::ConnectorData, + customer: &'a Option, + merchant_connector_id: &'a id_type::MerchantConnectorAccountId, +) -> (bool, Option<&'a str>) { + // Check if create customer is required for the connector + match enums::PayoutConnectors::try_from(connector.connector_name) { + Ok(connector) => { + let connector_needs_customer = state + .conf + .connector_customer + .payout_connector_list + .contains(&connector); + + if connector_needs_customer { + let connector_customer_details = customer + .as_ref() + .and_then(|customer| customer.get_connector_customer_id(merchant_connector_id)); let should_call_connector = connector_customer_details.is_none(); (should_call_connector, connector_customer_details) } else { diff --git a/crates/router/src/types/storage/customers.rs b/crates/router/src/types/storage/customers.rs index 8566d7365a4..507c219534d 100644 --- a/crates/router/src/types/storage/customers.rs +++ b/crates/router/src/types/storage/customers.rs @@ -1,3 +1,5 @@ pub use diesel_models::customers::{Customer, CustomerNew, CustomerUpdateInternal}; +#[cfg(all(feature = "v2", feature = "customer_v2"))] +pub use crate::types::domain::CustomerGeneralUpdate; pub use crate::types::domain::CustomerUpdate;