mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 12:06:56 +08:00
1403 lines
51 KiB
Rust
1403 lines
51 KiB
Rust
use common_utils::{
|
|
crypto::Encryptable,
|
|
errors::ReportSwitchExt,
|
|
ext_traits::AsyncExt,
|
|
id_type, pii, type_name,
|
|
types::{
|
|
keymanager::{Identifier, KeyManagerState, ToEncryptable},
|
|
Description,
|
|
},
|
|
};
|
|
use error_stack::{report, ResultExt};
|
|
use masking::{ExposeInterface, Secret, SwitchStrategy};
|
|
use router_env::{instrument, tracing};
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
use crate::core::payment_methods::cards::create_encrypted_data;
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
use crate::utils::CustomerAddress;
|
|
use crate::{
|
|
core::{
|
|
errors::{self, StorageErrorExt},
|
|
payment_methods::{cards, network_tokenization},
|
|
},
|
|
db::StorageInterface,
|
|
pii::PeekInterface,
|
|
routes::{metrics, SessionState},
|
|
services,
|
|
types::{
|
|
api::customers,
|
|
domain::{self, types},
|
|
storage::{self, enums},
|
|
transformers::ForeignFrom,
|
|
},
|
|
};
|
|
|
|
pub const REDACTED: &str = "Redacted";
|
|
|
|
#[instrument(skip(state))]
|
|
pub async fn create_customer(
|
|
state: SessionState,
|
|
merchant_account: domain::MerchantAccount,
|
|
key_store: domain::MerchantKeyStore,
|
|
customer_data: customers::CustomerRequest,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
|
let db: &dyn StorageInterface = state.store.as_ref();
|
|
let key_manager_state = &(&state).into();
|
|
|
|
let merchant_reference_id = customer_data.get_merchant_reference_id();
|
|
|
|
let merchant_id = merchant_account.get_id();
|
|
|
|
let merchant_reference_id_customer = MerchantReferenceIdForCustomer {
|
|
merchant_reference_id: merchant_reference_id.as_ref(),
|
|
merchant_id,
|
|
merchant_account: &merchant_account,
|
|
key_store: &key_store,
|
|
key_manager_state,
|
|
};
|
|
|
|
// We first need to validate whether the customer with the given customer id already exists
|
|
// this may seem like a redundant db call, as the insert_customer will anyway return this error
|
|
//
|
|
// Consider a scenario where the address is inserted and then when inserting the customer,
|
|
// it errors out, now the address that was inserted is not deleted
|
|
|
|
merchant_reference_id_customer
|
|
.verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(db)
|
|
.await?;
|
|
|
|
let domain_customer = customer_data
|
|
.create_domain_model_from_request(
|
|
db,
|
|
&key_store,
|
|
&merchant_reference_id,
|
|
&merchant_account,
|
|
key_manager_state,
|
|
&state,
|
|
)
|
|
.await?;
|
|
|
|
let customer = db
|
|
.insert_customer(
|
|
domain_customer,
|
|
key_manager_state,
|
|
&key_store,
|
|
merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.to_duplicate_response(errors::CustomersErrorResponse::CustomerAlreadyExists)?;
|
|
|
|
customer_data.generate_response(&customer)
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
trait CustomerCreateBridge {
|
|
async fn create_domain_model_from_request<'a>(
|
|
&'a self,
|
|
db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse>;
|
|
|
|
fn generate_response<'a>(
|
|
&'a self,
|
|
customer: &'a domain::Customer,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse>;
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
#[async_trait::async_trait]
|
|
impl CustomerCreateBridge for customers::CustomerRequest {
|
|
async fn create_domain_model_from_request<'a>(
|
|
&'a self,
|
|
db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse> {
|
|
// Setting default billing address to Db
|
|
let address = self.get_address();
|
|
let merchant_id = merchant_account.get_id();
|
|
let key = key_store.key.get_inner().peek();
|
|
|
|
let customer_billing_address_struct = AddressStructForDbEntry {
|
|
address: address.as_ref(),
|
|
customer_data: self,
|
|
merchant_id,
|
|
customer_id: merchant_reference_id.as_ref(),
|
|
storage_scheme: merchant_account.storage_scheme,
|
|
key_store,
|
|
key_manager_state,
|
|
state,
|
|
};
|
|
|
|
let address_from_db = customer_billing_address_struct
|
|
.encrypt_customer_address_and_set_to_db(db)
|
|
.await?;
|
|
|
|
let encrypted_data = types::crypto_operation(
|
|
key_manager_state,
|
|
type_name!(domain::Customer),
|
|
types::CryptoOperation::BatchEncrypt(
|
|
domain::FromRequestEncryptableCustomer::to_encryptable(
|
|
domain::FromRequestEncryptableCustomer {
|
|
name: self.name.clone(),
|
|
email: self.email.clone().map(|a| a.expose().switch_strategy()),
|
|
phone: self.phone.clone(),
|
|
},
|
|
),
|
|
),
|
|
Identifier::Merchant(key_store.merchant_id.clone()),
|
|
key,
|
|
)
|
|
.await
|
|
.and_then(|val| val.try_into_batchoperation())
|
|
.switch()
|
|
.attach_printable("Failed while encrypting Customer")?;
|
|
|
|
let encryptable_customer =
|
|
domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)?;
|
|
|
|
Ok(domain::Customer {
|
|
customer_id: merchant_reference_id
|
|
.to_owned()
|
|
.ok_or(errors::CustomersErrorResponse::InternalServerError)?,
|
|
merchant_id: merchant_id.to_owned(),
|
|
name: encryptable_customer.name,
|
|
email: encryptable_customer.email.map(|email| {
|
|
let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
|
|
email.clone().into_inner().switch_strategy(),
|
|
email.into_encrypted(),
|
|
);
|
|
encryptable
|
|
}),
|
|
phone: encryptable_customer.phone,
|
|
description: self.description.clone(),
|
|
phone_country_code: self.phone_country_code.clone(),
|
|
metadata: self.metadata.clone(),
|
|
connector_customer: None,
|
|
address_id: address_from_db.clone().map(|addr| addr.address_id),
|
|
created_at: common_utils::date_time::now(),
|
|
modified_at: common_utils::date_time::now(),
|
|
default_payment_method_id: None,
|
|
updated_by: None,
|
|
version: hyperswitch_domain_models::consts::API_VERSION,
|
|
})
|
|
}
|
|
|
|
fn generate_response<'a>(
|
|
&'a self,
|
|
customer: &'a domain::Customer,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
|
let address = self.get_address();
|
|
let address_details = address.map(api_models::payments::AddressDetails::from);
|
|
|
|
Ok(services::ApplicationResponse::Json(
|
|
customers::CustomerResponse::foreign_from((customer.clone(), address_details)),
|
|
))
|
|
}
|
|
}
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
#[async_trait::async_trait]
|
|
impl CustomerCreateBridge for customers::CustomerRequest {
|
|
async fn create_domain_model_from_request<'a>(
|
|
&'a self,
|
|
_db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse> {
|
|
let default_customer_billing_address = self.get_default_customer_billing_address();
|
|
let encrypted_customer_billing_address = default_customer_billing_address
|
|
.async_map(|billing_address| {
|
|
create_encrypted_data(key_state, key_store, billing_address)
|
|
})
|
|
.await
|
|
.transpose()
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)
|
|
.attach_printable("Unable to encrypt default customer billing address")?;
|
|
let default_customer_shipping_address = self.get_default_customer_shipping_address();
|
|
let encrypted_customer_shipping_address = default_customer_shipping_address
|
|
.async_map(|shipping_address| {
|
|
create_encrypted_data(key_state, key_store, shipping_address)
|
|
})
|
|
.await
|
|
.transpose()
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)
|
|
.attach_printable("Unable to encrypt default customer shipping address")?;
|
|
|
|
let merchant_id = merchant_account.get_id().clone();
|
|
let key = key_store.key.get_inner().peek();
|
|
|
|
let encrypted_data = types::crypto_operation(
|
|
key_state,
|
|
type_name!(domain::Customer),
|
|
types::CryptoOperation::BatchEncrypt(
|
|
domain::FromRequestEncryptableCustomer::to_encryptable(
|
|
domain::FromRequestEncryptableCustomer {
|
|
name: Some(self.name.clone()),
|
|
email: Some(self.email.clone().expose().switch_strategy()),
|
|
phone: self.phone.clone(),
|
|
},
|
|
),
|
|
),
|
|
Identifier::Merchant(key_store.merchant_id.clone()),
|
|
key,
|
|
)
|
|
.await
|
|
.and_then(|val| val.try_into_batchoperation())
|
|
.switch()
|
|
.attach_printable("Failed while encrypting Customer")?;
|
|
|
|
let encryptable_customer =
|
|
domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)?;
|
|
|
|
Ok(domain::Customer {
|
|
id: id_type::GlobalCustomerId::generate(&state.conf.cell_information.id),
|
|
merchant_reference_id: merchant_reference_id.to_owned(),
|
|
merchant_id,
|
|
name: encryptable_customer.name,
|
|
email: encryptable_customer.email.map(|email| {
|
|
let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
|
|
email.clone().into_inner().switch_strategy(),
|
|
email.into_encrypted(),
|
|
);
|
|
encryptable
|
|
}),
|
|
phone: encryptable_customer.phone,
|
|
description: self.description.clone(),
|
|
phone_country_code: self.phone_country_code.clone(),
|
|
metadata: self.metadata.clone(),
|
|
connector_customer: None,
|
|
created_at: common_utils::date_time::now(),
|
|
modified_at: common_utils::date_time::now(),
|
|
default_payment_method_id: None,
|
|
updated_by: None,
|
|
default_billing_address: encrypted_customer_billing_address.map(Into::into),
|
|
default_shipping_address: encrypted_customer_shipping_address.map(Into::into),
|
|
version: hyperswitch_domain_models::consts::API_VERSION,
|
|
status: common_enums::DeleteStatus::Active,
|
|
})
|
|
}
|
|
|
|
fn generate_response<'a>(
|
|
&'a self,
|
|
customer: &'a domain::Customer,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
|
Ok(services::ApplicationResponse::Json(
|
|
customers::CustomerResponse::foreign_from(customer.clone()),
|
|
))
|
|
}
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
struct AddressStructForDbEntry<'a> {
|
|
address: Option<&'a api_models::payments::AddressDetails>,
|
|
customer_data: &'a customers::CustomerRequest,
|
|
merchant_id: &'a id_type::MerchantId,
|
|
customer_id: Option<&'a id_type::CustomerId>,
|
|
storage_scheme: common_enums::MerchantStorageScheme,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
impl AddressStructForDbEntry<'_> {
|
|
async fn encrypt_customer_address_and_set_to_db(
|
|
&self,
|
|
db: &dyn StorageInterface,
|
|
) -> errors::CustomResult<Option<domain::Address>, errors::CustomersErrorResponse> {
|
|
let encrypted_customer_address = self
|
|
.address
|
|
.async_map(|addr| async {
|
|
self.customer_data
|
|
.get_domain_address(
|
|
self.state,
|
|
addr.clone(),
|
|
self.merchant_id,
|
|
self.customer_id
|
|
.ok_or(errors::CustomersErrorResponse::InternalServerError)?, // should we raise error since in v1 appilcation is supposed to have this id or generate it at this point.
|
|
self.key_store.key.get_inner().peek(),
|
|
self.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()
|
|
.attach_printable("Failed while encrypting address")
|
|
})
|
|
.await
|
|
.transpose()?;
|
|
|
|
encrypted_customer_address
|
|
.async_map(|encrypt_add| async {
|
|
db.insert_address_for_customers(self.key_manager_state, encrypt_add, self.key_store)
|
|
.await
|
|
.switch()
|
|
.attach_printable("Failed while inserting new address")
|
|
})
|
|
.await
|
|
.transpose()
|
|
}
|
|
}
|
|
|
|
struct MerchantReferenceIdForCustomer<'a> {
|
|
merchant_reference_id: Option<&'a id_type::CustomerId>,
|
|
merchant_id: &'a id_type::MerchantId,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
key_manager_state: &'a KeyManagerState,
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
impl<'a> MerchantReferenceIdForCustomer<'a> {
|
|
async fn verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(
|
|
&self,
|
|
db: &dyn StorageInterface,
|
|
) -> Result<Option<()>, error_stack::Report<errors::CustomersErrorResponse>> {
|
|
self.merchant_reference_id
|
|
.async_map(|cust| async {
|
|
self.verify_if_merchant_reference_not_present_by_merchant_reference_id(cust, db)
|
|
.await
|
|
})
|
|
.await
|
|
.transpose()
|
|
}
|
|
|
|
async fn verify_if_merchant_reference_not_present_by_merchant_reference_id(
|
|
&self,
|
|
cus: &'a id_type::CustomerId,
|
|
db: &dyn StorageInterface,
|
|
) -> Result<(), error_stack::Report<errors::CustomersErrorResponse>> {
|
|
match db
|
|
.find_customer_by_customer_id_merchant_id(
|
|
self.key_manager_state,
|
|
cus,
|
|
self.merchant_id,
|
|
self.key_store,
|
|
self.merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
{
|
|
Err(err) => {
|
|
if !err.current_context().is_db_not_found() {
|
|
Err(err).switch()
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
Ok(_) => Err(report!(
|
|
errors::CustomersErrorResponse::CustomerAlreadyExists
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
impl<'a> MerchantReferenceIdForCustomer<'a> {
|
|
async fn verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(
|
|
&self,
|
|
db: &dyn StorageInterface,
|
|
) -> Result<Option<()>, error_stack::Report<errors::CustomersErrorResponse>> {
|
|
self.merchant_reference_id
|
|
.async_map(|merchant_ref| async {
|
|
self.verify_if_merchant_reference_not_present_by_merchant_reference(
|
|
merchant_ref,
|
|
db,
|
|
)
|
|
.await
|
|
})
|
|
.await
|
|
.transpose()
|
|
}
|
|
|
|
async fn verify_if_merchant_reference_not_present_by_merchant_reference(
|
|
&self,
|
|
merchant_ref: &'a id_type::CustomerId,
|
|
db: &dyn StorageInterface,
|
|
) -> Result<(), error_stack::Report<errors::CustomersErrorResponse>> {
|
|
match db
|
|
.find_customer_by_merchant_reference_id_merchant_id(
|
|
self.key_manager_state,
|
|
merchant_ref,
|
|
self.merchant_id,
|
|
self.key_store,
|
|
self.merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
{
|
|
Err(err) => {
|
|
if !err.current_context().is_db_not_found() {
|
|
Err(err).switch()
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
Ok(_) => Err(report!(
|
|
errors::CustomersErrorResponse::CustomerAlreadyExists
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2",), not(feature = "customer_v2")))]
|
|
#[instrument(skip(state))]
|
|
pub async fn retrieve_customer(
|
|
state: SessionState,
|
|
merchant_account: domain::MerchantAccount,
|
|
_profile_id: Option<id_type::ProfileId>,
|
|
key_store: domain::MerchantKeyStore,
|
|
customer_id: id_type::CustomerId,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
|
let db = state.store.as_ref();
|
|
let key_manager_state = &(&state).into();
|
|
|
|
let response = db
|
|
.find_customer_optional_with_redacted_customer_details_by_customer_id_merchant_id(
|
|
key_manager_state,
|
|
&customer_id,
|
|
merchant_account.get_id(),
|
|
&key_store,
|
|
merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?
|
|
.ok_or(errors::CustomersErrorResponse::CustomerNotFound)?;
|
|
|
|
let address = match &response.address_id {
|
|
Some(address_id) => Some(api_models::payments::AddressDetails::from(
|
|
db.find_address_by_address_id(key_manager_state, address_id, &key_store)
|
|
.await
|
|
.switch()?,
|
|
)),
|
|
None => None,
|
|
};
|
|
Ok(services::ApplicationResponse::Json(
|
|
customers::CustomerResponse::foreign_from((response, address)),
|
|
))
|
|
}
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
#[instrument(skip(state))]
|
|
pub async fn retrieve_customer(
|
|
state: SessionState,
|
|
merchant_account: domain::MerchantAccount,
|
|
key_store: domain::MerchantKeyStore,
|
|
id: id_type::GlobalCustomerId,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
|
let db = state.store.as_ref();
|
|
let key_manager_state = &(&state).into();
|
|
|
|
let response = db
|
|
.find_customer_by_global_id(
|
|
key_manager_state,
|
|
&id,
|
|
merchant_account.get_id(),
|
|
&key_store,
|
|
merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
Ok(services::ApplicationResponse::Json(
|
|
customers::CustomerResponse::foreign_from(response),
|
|
))
|
|
}
|
|
|
|
#[instrument(skip(state))]
|
|
pub async fn list_customers(
|
|
state: SessionState,
|
|
merchant_id: id_type::MerchantId,
|
|
_profile_id_list: Option<Vec<id_type::ProfileId>>,
|
|
key_store: domain::MerchantKeyStore,
|
|
request: customers::CustomerListRequest,
|
|
) -> errors::CustomerResponse<Vec<customers::CustomerResponse>> {
|
|
let db = state.store.as_ref();
|
|
|
|
let customer_list_constraints = crate::db::customers::CustomerListConstraints {
|
|
limit: request
|
|
.limit
|
|
.unwrap_or(crate::consts::DEFAULT_LIST_API_LIMIT),
|
|
offset: request.offset,
|
|
};
|
|
|
|
let domain_customers = db
|
|
.list_customers_by_merchant_id(
|
|
&(&state).into(),
|
|
&merchant_id,
|
|
&key_store,
|
|
customer_list_constraints,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
let customers = domain_customers
|
|
.into_iter()
|
|
.map(|domain_customer| customers::CustomerResponse::foreign_from((domain_customer, None)))
|
|
.collect();
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
let customers = domain_customers
|
|
.into_iter()
|
|
.map(customers::CustomerResponse::foreign_from)
|
|
.collect();
|
|
|
|
Ok(services::ApplicationResponse::Json(customers))
|
|
}
|
|
|
|
#[cfg(all(
|
|
feature = "v2",
|
|
feature = "customer_v2",
|
|
feature = "payment_methods_v2"
|
|
))]
|
|
#[instrument(skip_all)]
|
|
pub async fn delete_customer(
|
|
state: SessionState,
|
|
merchant_account: domain::MerchantAccount,
|
|
id: id_type::GlobalCustomerId,
|
|
key_store: domain::MerchantKeyStore,
|
|
) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
|
|
let db = &*state.store;
|
|
let key_manager_state = &(&state).into();
|
|
id.redact_customer_details_and_generate_response(
|
|
db,
|
|
&key_store,
|
|
&merchant_account,
|
|
key_manager_state,
|
|
&state,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[cfg(all(
|
|
feature = "v2",
|
|
feature = "customer_v2",
|
|
feature = "payment_methods_v2"
|
|
))]
|
|
#[async_trait::async_trait]
|
|
impl CustomerDeleteBridge for id_type::GlobalCustomerId {
|
|
async fn redact_customer_details_and_generate_response<'a>(
|
|
&'a self,
|
|
db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
|
|
let customer_orig = db
|
|
.find_customer_by_global_id(
|
|
key_manager_state,
|
|
self,
|
|
merchant_account.get_id(),
|
|
key_store,
|
|
merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
let merchant_reference_id = customer_orig.merchant_reference_id.clone();
|
|
|
|
let customer_mandates = db.find_mandate_by_global_customer_id(self).await.switch()?;
|
|
|
|
for mandate in customer_mandates.into_iter() {
|
|
if mandate.mandate_status == enums::MandateStatus::Active {
|
|
Err(errors::CustomersErrorResponse::MandateActive)?
|
|
}
|
|
}
|
|
|
|
match db
|
|
.find_payment_method_list_by_global_customer_id(
|
|
key_manager_state,
|
|
key_store,
|
|
self,
|
|
None,
|
|
)
|
|
.await
|
|
{
|
|
// check this in review
|
|
Ok(customer_payment_methods) => {
|
|
for pm in customer_payment_methods.into_iter() {
|
|
if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) {
|
|
cards::delete_card_by_locker_id(state, self, merchant_account.get_id())
|
|
.await
|
|
.switch()?;
|
|
}
|
|
// No solution as of now, need to discuss this further with payment_method_v2
|
|
|
|
// db.delete_payment_method(
|
|
// key_manager_state,
|
|
// key_store,
|
|
// pm,
|
|
// )
|
|
// .await
|
|
// .switch()?;
|
|
}
|
|
}
|
|
Err(error) => {
|
|
if error.current_context().is_db_not_found() {
|
|
Ok(())
|
|
} else {
|
|
Err(error)
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)
|
|
.attach_printable(
|
|
"failed find_payment_method_by_customer_id_merchant_id_list",
|
|
)
|
|
}?
|
|
}
|
|
};
|
|
|
|
let key = key_store.key.get_inner().peek();
|
|
|
|
let identifier = Identifier::Merchant(key_store.merchant_id.clone());
|
|
let redacted_encrypted_value: Encryptable<Secret<_>> = types::crypto_operation(
|
|
key_manager_state,
|
|
type_name!(storage::Address),
|
|
types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
|
|
identifier.clone(),
|
|
key,
|
|
)
|
|
.await
|
|
.and_then(|val| val.try_into_operation())
|
|
.switch()?;
|
|
|
|
let redacted_encrypted_email = Encryptable::new(
|
|
redacted_encrypted_value
|
|
.clone()
|
|
.into_inner()
|
|
.switch_strategy(),
|
|
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),
|
|
};
|
|
|
|
db.update_customer_by_global_id(
|
|
key_manager_state,
|
|
self,
|
|
customer_orig,
|
|
merchant_account.get_id(),
|
|
updated_customer,
|
|
key_store,
|
|
merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
let response = customers::CustomerDeleteResponse {
|
|
id: self.clone(),
|
|
merchant_reference_id,
|
|
customer_deleted: true,
|
|
address_deleted: true,
|
|
payment_methods_deleted: true,
|
|
};
|
|
metrics::CUSTOMER_REDACTED.add(1, &[]);
|
|
Ok(services::ApplicationResponse::Json(response))
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
trait CustomerDeleteBridge {
|
|
async fn redact_customer_details_and_generate_response<'a>(
|
|
&'a self,
|
|
db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
) -> errors::CustomerResponse<customers::CustomerDeleteResponse>;
|
|
}
|
|
|
|
#[cfg(all(
|
|
any(feature = "v1", feature = "v2"),
|
|
not(feature = "customer_v2"),
|
|
not(feature = "payment_methods_v2")
|
|
))]
|
|
#[instrument(skip_all)]
|
|
pub async fn delete_customer(
|
|
state: SessionState,
|
|
merchant_account: domain::MerchantAccount,
|
|
customer_id: id_type::CustomerId,
|
|
key_store: domain::MerchantKeyStore,
|
|
) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
|
|
let db = &*state.store;
|
|
let key_manager_state = &(&state).into();
|
|
customer_id
|
|
.redact_customer_details_and_generate_response(
|
|
db,
|
|
&key_store,
|
|
&merchant_account,
|
|
key_manager_state,
|
|
&state,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[cfg(all(
|
|
any(feature = "v1", feature = "v2"),
|
|
not(feature = "customer_v2"),
|
|
not(feature = "payment_methods_v2")
|
|
))]
|
|
#[async_trait::async_trait]
|
|
impl CustomerDeleteBridge for id_type::CustomerId {
|
|
async fn redact_customer_details_and_generate_response<'a>(
|
|
&'a self,
|
|
db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
|
|
let customer_orig = db
|
|
.find_customer_by_customer_id_merchant_id(
|
|
key_manager_state,
|
|
self,
|
|
merchant_account.get_id(),
|
|
key_store,
|
|
merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
let customer_mandates = db
|
|
.find_mandate_by_merchant_id_customer_id(merchant_account.get_id(), self)
|
|
.await
|
|
.switch()?;
|
|
|
|
for mandate in customer_mandates.into_iter() {
|
|
if mandate.mandate_status == enums::MandateStatus::Active {
|
|
Err(errors::CustomersErrorResponse::MandateActive)?
|
|
}
|
|
}
|
|
|
|
match db
|
|
.find_payment_method_by_customer_id_merchant_id_list(
|
|
key_manager_state,
|
|
key_store,
|
|
self,
|
|
merchant_account.get_id(),
|
|
None,
|
|
)
|
|
.await
|
|
{
|
|
// check this in review
|
|
Ok(customer_payment_methods) => {
|
|
for pm in customer_payment_methods.into_iter() {
|
|
if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) {
|
|
cards::delete_card_from_locker(
|
|
state,
|
|
self,
|
|
merchant_account.get_id(),
|
|
pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
if let Some(network_token_ref_id) = pm.network_token_requestor_reference_id
|
|
{
|
|
network_tokenization::delete_network_token_from_locker_and_token_service(
|
|
state,
|
|
self,
|
|
merchant_account.get_id(),
|
|
pm.payment_method_id.clone(),
|
|
pm.network_token_locker_id,
|
|
network_token_ref_id,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
}
|
|
}
|
|
|
|
db.delete_payment_method_by_merchant_id_payment_method_id(
|
|
key_manager_state,
|
|
key_store,
|
|
merchant_account.get_id(),
|
|
&pm.payment_method_id,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
}
|
|
}
|
|
Err(error) => {
|
|
if error.current_context().is_db_not_found() {
|
|
Ok(())
|
|
} else {
|
|
Err(error)
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)
|
|
.attach_printable(
|
|
"failed find_payment_method_by_customer_id_merchant_id_list",
|
|
)
|
|
}?
|
|
}
|
|
};
|
|
|
|
let key = key_store.key.get_inner().peek();
|
|
let identifier = Identifier::Merchant(key_store.merchant_id.clone());
|
|
let redacted_encrypted_value: Encryptable<Secret<_>> = types::crypto_operation(
|
|
key_manager_state,
|
|
type_name!(storage::Address),
|
|
types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
|
|
identifier.clone(),
|
|
key,
|
|
)
|
|
.await
|
|
.and_then(|val| val.try_into_operation())
|
|
.switch()?;
|
|
|
|
let redacted_encrypted_email = Encryptable::new(
|
|
redacted_encrypted_value
|
|
.clone()
|
|
.into_inner()
|
|
.switch_strategy(),
|
|
redacted_encrypted_value.clone().into_encrypted(),
|
|
);
|
|
|
|
let update_address = storage::AddressUpdate::Update {
|
|
city: Some(REDACTED.to_string()),
|
|
country: None,
|
|
line1: Some(redacted_encrypted_value.clone()),
|
|
line2: Some(redacted_encrypted_value.clone()),
|
|
line3: Some(redacted_encrypted_value.clone()),
|
|
state: Some(redacted_encrypted_value.clone()),
|
|
zip: Some(redacted_encrypted_value.clone()),
|
|
first_name: Some(redacted_encrypted_value.clone()),
|
|
last_name: Some(redacted_encrypted_value.clone()),
|
|
phone_number: Some(redacted_encrypted_value.clone()),
|
|
country_code: Some(REDACTED.to_string()),
|
|
updated_by: merchant_account.storage_scheme.to_string(),
|
|
email: Some(redacted_encrypted_email),
|
|
};
|
|
|
|
match db
|
|
.update_address_by_merchant_id_customer_id(
|
|
key_manager_state,
|
|
self,
|
|
merchant_account.get_id(),
|
|
update_address,
|
|
key_store,
|
|
)
|
|
.await
|
|
{
|
|
Ok(_) => Ok(()),
|
|
Err(error) => {
|
|
if error.current_context().is_db_not_found() {
|
|
Ok(())
|
|
} else {
|
|
Err(error)
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)
|
|
.attach_printable("failed update_address_by_merchant_id_customer_id")
|
|
}
|
|
}
|
|
}?;
|
|
|
|
let updated_customer = storage::CustomerUpdate::Update {
|
|
name: Some(redacted_encrypted_value.clone()),
|
|
email: Some(
|
|
types::crypto_operation(
|
|
key_manager_state,
|
|
type_name!(storage::Customer),
|
|
types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
|
|
identifier,
|
|
key,
|
|
)
|
|
.await
|
|
.and_then(|val| val.try_into_operation())
|
|
.switch()?,
|
|
),
|
|
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),
|
|
address_id: None,
|
|
};
|
|
|
|
db.update_customer_by_customer_id_merchant_id(
|
|
key_manager_state,
|
|
self.clone(),
|
|
merchant_account.get_id().to_owned(),
|
|
customer_orig,
|
|
updated_customer,
|
|
key_store,
|
|
merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
let response = customers::CustomerDeleteResponse {
|
|
customer_id: self.clone(),
|
|
customer_deleted: true,
|
|
address_deleted: true,
|
|
payment_methods_deleted: true,
|
|
};
|
|
metrics::CUSTOMER_REDACTED.add(1, &[]);
|
|
Ok(services::ApplicationResponse::Json(response))
|
|
}
|
|
}
|
|
|
|
#[instrument(skip(state))]
|
|
pub async fn update_customer(
|
|
state: SessionState,
|
|
merchant_account: domain::MerchantAccount,
|
|
update_customer: customers::CustomerUpdateRequestInternal,
|
|
key_store: domain::MerchantKeyStore,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
|
let db = state.store.as_ref();
|
|
let key_manager_state = &(&state).into();
|
|
//Add this in update call if customer can be updated anywhere else
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
let verify_id_for_update_customer = VerifyIdForUpdateCustomer {
|
|
merchant_reference_id: &update_customer.customer_id,
|
|
merchant_account: &merchant_account,
|
|
key_store: &key_store,
|
|
key_manager_state,
|
|
};
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
let verify_id_for_update_customer = VerifyIdForUpdateCustomer {
|
|
id: &update_customer.id,
|
|
merchant_account: &merchant_account,
|
|
key_store: &key_store,
|
|
key_manager_state,
|
|
};
|
|
|
|
let customer = verify_id_for_update_customer
|
|
.verify_id_and_get_customer_object(db)
|
|
.await?;
|
|
|
|
let updated_customer = update_customer
|
|
.request
|
|
.create_domain_model_from_request(
|
|
db,
|
|
&key_store,
|
|
&merchant_account,
|
|
key_manager_state,
|
|
&state,
|
|
&customer,
|
|
)
|
|
.await?;
|
|
|
|
update_customer.request.generate_response(&updated_customer)
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
trait CustomerUpdateBridge {
|
|
async fn create_domain_model_from_request<'a>(
|
|
&'a self,
|
|
db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
domain_customer: &'a domain::Customer,
|
|
) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse>;
|
|
|
|
fn generate_response<'a>(
|
|
&'a self,
|
|
customer: &'a domain::Customer,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse>;
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
struct AddressStructForDbUpdate<'a> {
|
|
update_customer: &'a customers::CustomerUpdateRequest,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
domain_customer: &'a domain::Customer,
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
impl AddressStructForDbUpdate<'_> {
|
|
async fn update_address_if_sent(
|
|
&self,
|
|
db: &dyn StorageInterface,
|
|
) -> errors::CustomResult<Option<domain::Address>, errors::CustomersErrorResponse> {
|
|
let address = if let Some(addr) = &self.update_customer.address {
|
|
match self.domain_customer.address_id.clone() {
|
|
Some(address_id) => {
|
|
let customer_address: api_models::payments::AddressDetails = addr.clone();
|
|
let update_address = self
|
|
.update_customer
|
|
.get_address_update(
|
|
self.state,
|
|
customer_address,
|
|
self.key_store.key.get_inner().peek(),
|
|
self.merchant_account.storage_scheme,
|
|
self.merchant_account.get_id().clone(),
|
|
)
|
|
.await
|
|
.switch()
|
|
.attach_printable("Failed while encrypting Address while Update")?;
|
|
Some(
|
|
db.update_address(
|
|
self.key_manager_state,
|
|
address_id,
|
|
update_address,
|
|
self.key_store,
|
|
)
|
|
.await
|
|
.switch()
|
|
.attach_printable(format!(
|
|
"Failed while updating address: merchant_id: {:?}, customer_id: {:?}",
|
|
self.merchant_account.get_id(),
|
|
self.domain_customer.customer_id
|
|
))?,
|
|
)
|
|
}
|
|
None => {
|
|
let customer_address: api_models::payments::AddressDetails = addr.clone();
|
|
|
|
let address = self
|
|
.update_customer
|
|
.get_domain_address(
|
|
self.state,
|
|
customer_address,
|
|
self.merchant_account.get_id(),
|
|
&self.domain_customer.customer_id,
|
|
self.key_store.key.get_inner().peek(),
|
|
self.merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()
|
|
.attach_printable("Failed while encrypting address")?;
|
|
Some(
|
|
db.insert_address_for_customers(
|
|
self.key_manager_state,
|
|
address,
|
|
self.key_store,
|
|
)
|
|
.await
|
|
.switch()
|
|
.attach_printable("Failed while inserting new address")?,
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
match &self.domain_customer.address_id {
|
|
Some(address_id) => Some(
|
|
db.find_address_by_address_id(
|
|
self.key_manager_state,
|
|
address_id,
|
|
self.key_store,
|
|
)
|
|
.await
|
|
.switch()?,
|
|
),
|
|
None => None,
|
|
}
|
|
};
|
|
Ok(address)
|
|
}
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
#[derive(Debug)]
|
|
struct VerifyIdForUpdateCustomer<'a> {
|
|
merchant_reference_id: &'a id_type::CustomerId,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
key_manager_state: &'a KeyManagerState,
|
|
}
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
#[derive(Debug)]
|
|
struct VerifyIdForUpdateCustomer<'a> {
|
|
id: &'a id_type::GlobalCustomerId,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
key_manager_state: &'a KeyManagerState,
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
impl VerifyIdForUpdateCustomer<'_> {
|
|
async fn verify_id_and_get_customer_object(
|
|
&self,
|
|
db: &dyn StorageInterface,
|
|
) -> Result<domain::Customer, error_stack::Report<errors::CustomersErrorResponse>> {
|
|
let customer = db
|
|
.find_customer_by_customer_id_merchant_id(
|
|
self.key_manager_state,
|
|
self.merchant_reference_id,
|
|
self.merchant_account.get_id(),
|
|
self.key_store,
|
|
self.merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
Ok(customer)
|
|
}
|
|
}
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
impl VerifyIdForUpdateCustomer<'_> {
|
|
async fn verify_id_and_get_customer_object(
|
|
&self,
|
|
db: &dyn StorageInterface,
|
|
) -> Result<domain::Customer, error_stack::Report<errors::CustomersErrorResponse>> {
|
|
let customer = db
|
|
.find_customer_by_global_id(
|
|
self.key_manager_state,
|
|
self.id,
|
|
self.merchant_account.get_id(),
|
|
self.key_store,
|
|
self.merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
Ok(customer)
|
|
}
|
|
}
|
|
|
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
|
#[async_trait::async_trait]
|
|
impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
|
async fn create_domain_model_from_request<'a>(
|
|
&'a self,
|
|
db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
domain_customer: &'a domain::Customer,
|
|
) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse> {
|
|
let update_address_for_update_customer = AddressStructForDbUpdate {
|
|
update_customer: self,
|
|
merchant_account,
|
|
key_store,
|
|
key_manager_state,
|
|
state,
|
|
domain_customer,
|
|
};
|
|
|
|
let address = update_address_for_update_customer
|
|
.update_address_if_sent(db)
|
|
.await?;
|
|
|
|
let key = key_store.key.get_inner().peek();
|
|
|
|
let encrypted_data = types::crypto_operation(
|
|
key_manager_state,
|
|
type_name!(domain::Customer),
|
|
types::CryptoOperation::BatchEncrypt(
|
|
domain::FromRequestEncryptableCustomer::to_encryptable(
|
|
domain::FromRequestEncryptableCustomer {
|
|
name: self.name.clone(),
|
|
email: self
|
|
.email
|
|
.as_ref()
|
|
.map(|a| a.clone().expose().switch_strategy()),
|
|
phone: self.phone.clone(),
|
|
},
|
|
),
|
|
),
|
|
Identifier::Merchant(key_store.merchant_id.clone()),
|
|
key,
|
|
)
|
|
.await
|
|
.and_then(|val| val.try_into_batchoperation())
|
|
.switch()?;
|
|
|
|
let encryptable_customer =
|
|
domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)?;
|
|
|
|
let response = db
|
|
.update_customer_by_customer_id_merchant_id(
|
|
key_manager_state,
|
|
domain_customer.customer_id.to_owned(),
|
|
merchant_account.get_id().to_owned(),
|
|
domain_customer.to_owned(),
|
|
storage::CustomerUpdate::Update {
|
|
name: encryptable_customer.name,
|
|
email: encryptable_customer.email.map(|email| {
|
|
let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> =
|
|
Encryptable::new(
|
|
email.clone().into_inner().switch_strategy(),
|
|
email.into_encrypted(),
|
|
);
|
|
encryptable
|
|
}),
|
|
phone: Box::new(encryptable_customer.phone),
|
|
phone_country_code: self.phone_country_code.clone(),
|
|
metadata: self.metadata.clone(),
|
|
description: self.description.clone(),
|
|
connector_customer: Box::new(None),
|
|
address_id: address.clone().map(|addr| addr.address_id),
|
|
},
|
|
key_store,
|
|
merchant_account.storage_scheme,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
|
|
Ok(response)
|
|
}
|
|
|
|
fn generate_response<'a>(
|
|
&'a self,
|
|
customer: &'a domain::Customer,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
|
let address = self.get_address();
|
|
let address_details = address.map(api_models::payments::AddressDetails::from);
|
|
|
|
Ok(services::ApplicationResponse::Json(
|
|
customers::CustomerResponse::foreign_from((customer.clone(), address_details)),
|
|
))
|
|
}
|
|
}
|
|
|
|
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
|
#[async_trait::async_trait]
|
|
impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
|
async fn create_domain_model_from_request<'a>(
|
|
&'a self,
|
|
db: &'a dyn StorageInterface,
|
|
key_store: &'a domain::MerchantKeyStore,
|
|
merchant_account: &'a domain::MerchantAccount,
|
|
key_manager_state: &'a KeyManagerState,
|
|
state: &'a SessionState,
|
|
domain_customer: &'a domain::Customer,
|
|
) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse> {
|
|
let default_billing_address = self.get_default_customer_billing_address();
|
|
let encrypted_customer_billing_address = default_billing_address
|
|
.async_map(|billing_address| {
|
|
create_encrypted_data(key_manager_state, key_store, billing_address)
|
|
})
|
|
.await
|
|
.transpose()
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)
|
|
.attach_printable("Unable to encrypt default customer billing address")?;
|
|
|
|
let default_shipping_address = self.get_default_customer_shipping_address();
|
|
let encrypted_customer_shipping_address = default_shipping_address
|
|
.async_map(|shipping_address| {
|
|
create_encrypted_data(key_manager_state, key_store, shipping_address)
|
|
})
|
|
.await
|
|
.transpose()
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)
|
|
.attach_printable("Unable to encrypt default customer shipping address")?;
|
|
|
|
let key = key_store.key.get_inner().peek();
|
|
|
|
let encrypted_data = types::crypto_operation(
|
|
key_manager_state,
|
|
type_name!(domain::Customer),
|
|
types::CryptoOperation::BatchEncrypt(
|
|
domain::FromRequestEncryptableCustomer::to_encryptable(
|
|
domain::FromRequestEncryptableCustomer {
|
|
name: self.name.clone(),
|
|
email: self
|
|
.email
|
|
.as_ref()
|
|
.map(|a| a.clone().expose().switch_strategy()),
|
|
phone: self.phone.clone(),
|
|
},
|
|
),
|
|
),
|
|
Identifier::Merchant(key_store.merchant_id.clone()),
|
|
key,
|
|
)
|
|
.await
|
|
.and_then(|val| val.try_into_batchoperation())
|
|
.switch()?;
|
|
|
|
let encryptable_customer =
|
|
domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
|
|
.change_context(errors::CustomersErrorResponse::InternalServerError)?;
|
|
|
|
let response = db
|
|
.update_customer_by_global_id(
|
|
key_manager_state,
|
|
&domain_customer.id,
|
|
domain_customer.to_owned(),
|
|
merchant_account.get_id(),
|
|
storage::CustomerUpdate::Update {
|
|
name: encryptable_customer.name,
|
|
email: Box::new(encryptable_customer.email.map(|email| {
|
|
let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> =
|
|
Encryptable::new(
|
|
email.clone().into_inner().switch_strategy(),
|
|
email.into_encrypted(),
|
|
);
|
|
encryptable
|
|
})),
|
|
phone: Box::new(encryptable_customer.phone),
|
|
phone_country_code: self.phone_country_code.clone(),
|
|
metadata: self.metadata.clone(),
|
|
description: self.description.clone(),
|
|
connector_customer: Box::new(None),
|
|
default_billing_address: encrypted_customer_billing_address.map(Into::into),
|
|
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,
|
|
)
|
|
.await
|
|
.switch()?;
|
|
Ok(response)
|
|
}
|
|
|
|
fn generate_response<'a>(
|
|
&'a self,
|
|
customer: &'a domain::Customer,
|
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
|
Ok(services::ApplicationResponse::Json(
|
|
customers::CustomerResponse::foreign_from(customer.clone()),
|
|
))
|
|
}
|
|
}
|
|
|
|
pub async fn migrate_customers(
|
|
state: SessionState,
|
|
customers: Vec<customers::CustomerRequest>,
|
|
merchant_account: domain::MerchantAccount,
|
|
key_store: domain::MerchantKeyStore,
|
|
) -> errors::CustomerResponse<()> {
|
|
for customer in customers {
|
|
match create_customer(
|
|
state.clone(),
|
|
merchant_account.clone(),
|
|
key_store.clone(),
|
|
customer,
|
|
)
|
|
.await
|
|
{
|
|
Ok(_) => (),
|
|
Err(e) => match e.current_context() {
|
|
errors::CustomersErrorResponse::CustomerAlreadyExists => (),
|
|
_ => return Err(e),
|
|
},
|
|
}
|
|
}
|
|
Ok(services::ApplicationResponse::Json(()))
|
|
}
|