diff --git a/crates/diesel_models/src/address.rs b/crates/diesel_models/src/address.rs index 71ec7c1b89..828a34f9a2 100644 --- a/crates/diesel_models/src/address.rs +++ b/crates/diesel_models/src/address.rs @@ -20,14 +20,15 @@ pub struct AddressNew { pub country_code: Option, pub customer_id: String, pub merchant_id: String, + pub payment_id: Option, pub created_at: PrimitiveDateTime, pub modified_at: PrimitiveDateTime, } -#[derive(Clone, Debug, Identifiable, Queryable)] -#[diesel(table_name = address)] +#[derive(Clone, Debug, Queryable, Identifiable)] +#[diesel(table_name = address, primary_key(address_id))] pub struct Address { - pub id: i32, + pub id: Option, pub address_id: String, pub city: Option, pub country: Option, @@ -44,6 +45,7 @@ pub struct Address { pub modified_at: PrimitiveDateTime, pub customer_id: String, pub merchant_id: String, + pub payment_id: Option, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs index 68ae2aba01..c0cebba775 100644 --- a/crates/diesel_models/src/customers.rs +++ b/crates/diesel_models/src/customers.rs @@ -18,6 +18,7 @@ pub struct CustomerNew { pub connector_customer: Option, pub created_at: PrimitiveDateTime, pub modified_at: PrimitiveDateTime, + pub address_id: Option, } #[derive(Clone, Debug, Identifiable, Queryable)] @@ -35,6 +36,7 @@ pub struct Customer { pub metadata: Option, pub connector_customer: Option, pub modified_at: PrimitiveDateTime, + pub address_id: Option, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] @@ -48,4 +50,5 @@ pub struct CustomerUpdateInternal { pub metadata: Option, pub modified_at: Option, pub connector_customer: Option, + pub address_id: Option, } diff --git a/crates/diesel_models/src/query/address.rs b/crates/diesel_models/src/query/address.rs index 4474154cf3..77de21d2d9 100644 --- a/crates/diesel_models/src/query/address.rs +++ b/crates/diesel_models/src/query/address.rs @@ -76,12 +76,33 @@ impl Address { } #[instrument(skip(conn))] - pub async fn find_by_address_id<'a>( + pub async fn find_by_merchant_id_payment_id_address_id<'a>( conn: &PgPooledConn, + merchant_id: &str, + payment_id: &str, address_id: &str, ) -> StorageResult { - generics::generic_find_by_id::<::Table, _, _>(conn, address_id.to_owned()) - .await + match generics::generic_find_one::<::Table, _, _>( + conn, + dsl::payment_id + .eq(payment_id.to_owned()) + .and(dsl::merchant_id.eq(merchant_id.to_owned())) + .and(dsl::address_id.eq(address_id.to_owned())), + ) + .await + { + Err(error) => match error.current_context() { + errors::DatabaseError::NotFound => { + generics::generic_find_by_id::<::Table, _, _>( + conn, + address_id.to_owned(), + ) + .await + } + _ => Err(error), + }, + result => result, + } } #[instrument(skip(conn))] diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index bb434a5b81..5df442af43 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -5,7 +5,7 @@ diesel::table! { use crate::enums::diesel_exports::*; address (address_id) { - id -> Int4, + id -> Nullable, #[max_length = 64] address_id -> Varchar, #[max_length = 128] @@ -27,6 +27,8 @@ diesel::table! { customer_id -> Varchar, #[max_length = 64] merchant_id -> Varchar, + #[max_length = 64] + payment_id -> Nullable, } } @@ -197,6 +199,8 @@ diesel::table! { metadata -> Nullable, connector_customer -> Nullable, modified_at -> Timestamp, + #[max_length = 64] + address_id -> Nullable, } } diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index 00cfd3aced..dce80f77e3 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -7,7 +7,6 @@ use masking::ExposeInterface; use router_env::{instrument, tracing}; use crate::{ - consts, core::{ errors::{self}, payment_methods::cards, @@ -23,7 +22,7 @@ use crate::{ }, storage::{self, enums}, }, - utils::generate_id, + utils::CustomerAddress, }; pub const REDACTED: &str = "Redacted"; @@ -41,64 +40,25 @@ pub async fn create_customer( customer_data.merchant_id = merchant_id.to_owned(); let key = key_store.key.get_inner().peek(); - if let Some(addr) = &customer_data.address { + let address_id = if let Some(addr) = &customer_data.address { let customer_address: api_models::payments::AddressDetails = addr.clone(); - let address = async { - Ok(domain::Address { - city: customer_address.city, - country: customer_address.country, - line1: customer_address - .line1 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - line2: customer_address - .line2 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - line3: customer_address - .line3 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - zip: customer_address - .zip - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - state: customer_address - .state - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - first_name: customer_address - .first_name - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - last_name: customer_address - .last_name - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - phone_number: customer_data - .phone - .clone() - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - country_code: customer_data.phone_country_code.clone(), - customer_id: customer_id.to_string(), - merchant_id: merchant_id.to_string(), - id: None, - address_id: generate_id(consts::ID_LENGTH, "add"), - created_at: common_utils::date_time::now(), - modified_at: common_utils::date_time::now(), - }) - } - .await - .switch() - .attach_printable("Failed while encrypting address")?; - - db.insert_address(address, &key_store) + let address = customer_data + .get_domain_address(customer_address, merchant_id, customer_id, key) .await .switch() - .attach_printable("Failed while inserting new address")?; - } + .attach_printable("Failed while encrypting address")?; + + Some( + db.insert_address_for_customers(address, &key_store) + .await + .switch() + .attach_printable("Failed while inserting new address")? + .address_id, + ) + } else { + None + }; let new_customer = async { Ok(domain::Customer { @@ -121,6 +81,7 @@ pub async fn create_customer( metadata: customer_data.metadata, id: None, connector_customer: None, + address_id, created_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(), }) @@ -287,11 +248,12 @@ pub async fn delete_customer( .await .switch()?, ), - phone: Some(redacted_encrypted_value.clone()), + phone: Box::new(Some(redacted_encrypted_value.clone())), description: Some(REDACTED.to_string()), phone_country_code: Some(REDACTED.to_string()), metadata: None, connector_customer: None, + address_id: None, }; db.update_customer_by_customer_id_merchant_id( req.customer_id.clone(), @@ -321,73 +283,59 @@ pub async fn update_customer( ) -> errors::CustomerResponse { let db = state.store.as_ref(); //Add this in update call if customer can be updated anywhere else - db.find_customer_by_customer_id_merchant_id( - &update_customer.customer_id, - &merchant_account.merchant_id, - &key_store, - ) - .await - .switch()?; - - let key = key_store.key.get_inner().peek(); - - if let Some(addr) = &update_customer.address { - let customer_address: api_models::payments::AddressDetails = addr.clone(); - let update_address = async { - Ok(storage::AddressUpdate::Update { - city: customer_address.city, - country: customer_address.country, - line1: customer_address - .line1 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - line2: customer_address - .line2 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - line3: customer_address - .line3 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - zip: customer_address - .zip - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - state: customer_address - .state - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - first_name: customer_address - .first_name - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - last_name: customer_address - .last_name - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - phone_number: update_customer - .phone - .clone() - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - country_code: update_customer.phone_country_code.clone(), - }) - } - .await - .switch() - .attach_printable("Failed while encrypting Address while Update")?; - db.update_address_by_merchant_id_customer_id( + let customer = db + .find_customer_by_customer_id_merchant_id( &update_customer.customer_id, &merchant_account.merchant_id, - update_address, &key_store, ) .await - .switch() - .attach_printable(format!( - "Failed while updating address: merchant_id: {}, customer_id: {}", - merchant_account.merchant_id, update_customer.customer_id - ))?; + .switch()?; + + let key = key_store.key.get_inner().peek(); + + let address_id = if let Some(addr) = &update_customer.address { + match customer.address_id { + Some(address_id) => { + let customer_address: api_models::payments::AddressDetails = addr.clone(); + let update_address = update_customer + .get_address_update(customer_address, key) + .await + .switch() + .attach_printable("Failed while encrypting Address while Update")?; + db.update_address(address_id.clone(), update_address, &key_store) + .await + .switch() + .attach_printable(format!( + "Failed while updating address: merchant_id: {}, customer_id: {}", + merchant_account.merchant_id, update_customer.customer_id + ))?; + Some(address_id) + } + None => { + let customer_address: api_models::payments::AddressDetails = addr.clone(); + + let address = update_customer + .get_domain_address( + customer_address, + &merchant_account.merchant_id, + &customer.customer_id, + key, + ) + .await + .switch() + .attach_printable("Failed while encrypting address")?; + Some( + db.insert_address_for_customers(address, &key_store) + .await + .switch() + .attach_printable("Failed while inserting new address")? + .address_id, + ) + } + } + } else { + None }; let response = db @@ -406,14 +354,17 @@ pub async fn update_customer( types::encrypt_optional(inner.map(|inner| inner.expose()), key) }) .await?, - phone: update_customer - .phone - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, + phone: Box::new( + update_customer + .phone + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + ), phone_country_code: update_customer.phone_country_code, metadata: update_customer.metadata, description: update_customer.description, connector_customer: None, + address_id, }) } .await diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 83d2fd1361..24cbeec31e 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -796,7 +796,14 @@ pub async fn list_payment_methods( let shipping_address = payment_intent .as_ref() .async_map(|pi| async { - helpers::get_address_by_id(db, pi.shipping_address_id.clone(), &key_store).await + helpers::get_address_by_id( + db, + pi.shipping_address_id.clone(), + &key_store, + pi.payment_id.clone(), + merchant_account.merchant_id.clone(), + ) + .await }) .await .transpose()? @@ -805,7 +812,14 @@ pub async fn list_payment_methods( let billing_address = payment_intent .as_ref() .async_map(|pi| async { - helpers::get_address_by_id(db, pi.billing_address_id.clone(), &key_store).await + helpers::get_address_by_id( + db, + pi.billing_address_id.clone(), + &key_store, + pi.payment_id.clone(), + merchant_account.merchant_id.clone(), + ) + .await }) .await .transpose()? diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 7970d4d15b..2442a20286 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -104,176 +104,138 @@ pub fn filter_mca_based_on_business_profile( } #[instrument(skip_all)] -pub async fn get_address_for_payment_request( +pub async fn create_or_find_address_for_payment_by_request( db: &dyn StorageInterface, req_address: Option<&api::Address>, address_id: Option<&str>, merchant_id: &str, customer_id: Option<&String>, merchant_key_store: &domain::MerchantKeyStore, + payment_id: &str, ) -> CustomResult, errors::ApiErrorResponse> { let key = merchant_key_store.key.get_inner().peek(); - Ok(match req_address { - Some(address) => { - match address_id { - Some(id) => { - let address_update = async { - Ok(storage::AddressUpdate::Update { - city: address - .address - .as_ref() - .and_then(|value| value.city.clone()), - country: address.address.as_ref().and_then(|value| value.country), - line1: address - .address - .as_ref() - .and_then(|value| value.line1.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - line2: address - .address - .as_ref() - .and_then(|value| value.line2.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - line3: address - .address - .as_ref() - .and_then(|value| value.line3.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - state: address - .address - .as_ref() - .and_then(|value| value.state.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - zip: address - .address - .as_ref() - .and_then(|value| value.zip.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - first_name: address - .address - .as_ref() - .and_then(|value| value.first_name.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - last_name: address - .address - .as_ref() - .and_then(|value| value.last_name.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - phone_number: address - .phone - .as_ref() - .and_then(|value| value.number.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - country_code: address - .phone - .as_ref() - .and_then(|value| value.country_code.clone()), - }) - } - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while encrypting address")?; - Some( - db.update_address(id.to_owned(), address_update, merchant_key_store) - .await - .to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?, - ) - } - None => { - // generate a new address here - let customer_id = customer_id.get_required_value("customer_id")?; + Ok(match address_id { + Some(id) => Some( + db.find_address_by_merchant_id_payment_id_address_id( + merchant_id, + payment_id, + id, + merchant_key_store, + ) + .await, + ) + .transpose() + .to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?, + None => match req_address { + Some(address) => { + // generate a new address here + let customer_id = customer_id.get_required_value("customer_id")?; - let address_details = address.address.clone().unwrap_or_default(); - Some( - db.insert_address( - async { - Ok(domain::Address { - phone_number: address - .phone - .as_ref() - .and_then(|a| a.number.clone()) - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - country_code: address - .phone - .as_ref() - .and_then(|a| a.country_code.clone()), - customer_id: customer_id.to_string(), - merchant_id: merchant_id.to_string(), - address_id: generate_id(consts::ID_LENGTH, "add"), - city: address_details.city, - country: address_details.country, - line1: address_details - .line1 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - line2: address_details - .line2 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - line3: address_details - .line3 - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - id: None, - state: address_details - .state - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - created_at: common_utils::date_time::now(), - first_name: address_details - .first_name - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - last_name: address_details - .last_name - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - modified_at: common_utils::date_time::now(), - zip: address_details - .zip - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, - }) - } - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while encrypting address while insert")?, - merchant_key_store, + let address_details = address.address.clone().unwrap_or_default(); + Some( + db.insert_address_for_payments( + payment_id, + get_domain_address_for_payments( + address_details, + address, + merchant_id, + customer_id, + payment_id, + key, ) .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while inserting new address")?, + .attach_printable("Failed while encrypting address while insert")?, + merchant_key_store, ) - } + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while inserting new address")?, + ) } - } - None => match address_id { - Some(id) => Some(db.find_address(id, merchant_key_store).await) - .transpose() - .to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?, None => None, }, }) } +pub async fn get_domain_address_for_payments( + address_details: api_models::payments::AddressDetails, + address: &api_models::payments::Address, + merchant_id: &str, + customer_id: &str, + payment_id: &str, + key: &[u8], +) -> CustomResult { + async { + Ok(domain::Address { + id: None, + phone_number: address + .phone + .as_ref() + .and_then(|a| a.number.clone()) + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + country_code: address.phone.as_ref().and_then(|a| a.country_code.clone()), + customer_id: customer_id.to_string(), + merchant_id: merchant_id.to_string(), + address_id: generate_id(consts::ID_LENGTH, "add"), + city: address_details.city, + country: address_details.country, + line1: address_details + .line1 + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + line2: address_details + .line2 + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + line3: address_details + .line3 + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + state: address_details + .state + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + created_at: common_utils::date_time::now(), + first_name: address_details + .first_name + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + last_name: address_details + .last_name + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + modified_at: common_utils::date_time::now(), + zip: address_details + .zip + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + payment_id: Some(payment_id.to_owned()), + }) + } + .await +} + pub async fn get_address_by_id( db: &dyn StorageInterface, address_id: Option, merchant_key_store: &domain::MerchantKeyStore, + payment_id: String, + merchant_id: String, ) -> CustomResult, errors::ApiErrorResponse> { match address_id { None => Ok(None), - Some(address_id) => Ok(db.find_address(&address_id, merchant_key_store).await.ok()), + Some(address_id) => Ok(db + .find_address_by_merchant_id_payment_id_address_id( + &merchant_id, + &payment_id, + &address_id, + merchant_key_store, + ) + .await + .ok()), } } @@ -1081,15 +1043,18 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>( ) }) .await?, - phone: request_customer_details - .phone - .clone() - .async_lift(|inner| types::encrypt_optional(inner, key)) - .await?, + phone: Box::new( + request_customer_details + .phone + .clone() + .async_lift(|inner| types::encrypt_optional(inner, key)) + .await?, + ), phone_country_code: request_customer_details.phone_country_code, description: None, connector_customer: None, metadata: None, + address_id: None, }) } .await @@ -1136,6 +1101,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>( metadata: None, modified_at: common_utils::date_time::now(), connector_customer: None, + address_id: None, }) } .await diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index 3c58864568..708a6ebb90 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -136,22 +136,24 @@ impl GetTracker, api::PaymentsRequest> for Pa .or_else(|| request.customer_id.clone()), )?; - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, request.shipping.as_ref(), payment_intent.shipping_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, request.billing.as_ref(), payment_intent.billing_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index a657034c42..7efeb9a176 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -79,23 +79,25 @@ impl GetTracker, api::PaymentsCancelRequest> .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.shipping_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.billing_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 054f9a31b9..1c03f42757 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -153,23 +153,25 @@ impl GetTracker, api::PaymentsCaptu amount = payment_attempt.amount.into(); - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.shipping_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.billing_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 18315969d2..e72342e3c4 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -145,22 +145,24 @@ impl GetTracker, api::PaymentsRequest> for Co .or_else(|| request.customer_id.clone()), )?; - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, request.shipping.as_ref(), payment_intent.shipping_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, request.billing.as_ref(), payment_intent.billing_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index cb0fcac82a..19c63cc786 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -104,7 +104,7 @@ impl GetTracker, api::PaymentsRequest> for Pa ) .map(|x| x.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)); - let shipping_address_fut = helpers::get_address_for_payment_request( + let shipping_address_fut = helpers::create_or_find_address_for_payment_by_request( db, request.shipping.as_ref(), payment_intent.shipping_address_id.as_deref(), @@ -114,9 +114,10 @@ impl GetTracker, api::PaymentsRequest> for Pa .as_ref() .or(customer_details.customer_id.as_ref()), key_store, + &payment_intent.payment_id, ); - let billing_address_fut = helpers::get_address_for_payment_request( + let billing_address_fut = helpers::create_or_find_address_for_payment_by_request( db, request.billing.as_ref(), payment_intent.billing_address_id.as_deref(), @@ -126,6 +127,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .as_ref() .or(customer_details.customer_id.as_ref()), key_store, + &payment_intent.payment_id, ); let config_update_fut = request diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 32ab96c359..f877fc3762 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -92,23 +92,25 @@ impl GetTracker, api::PaymentsRequest> for Pa let customer_details = helpers::get_customer_details_from_request(request); - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, request.shipping.as_ref(), None, merchant_id, customer_details.customer_id.as_ref(), merchant_key_store, + &payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, request.billing.as_ref(), None, merchant_id, customer_details.customer_id.as_ref(), merchant_key_store, + &payment_id, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index 18a4df4fd6..61a9aede29 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -77,23 +77,25 @@ impl GetTracker, PaymentsRejectRequest> for P .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.shipping_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.billing_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 70d8c36f2f..47c95d8b79 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -90,23 +90,25 @@ impl GetTracker, api::PaymentsSessionRequest> let amount = payment_intent.amount.into(); - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.shipping_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.billing_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), key_store, + &payment_intent.payment_id, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index a93b9a4c4d..400678506f 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -85,22 +85,24 @@ impl GetTracker, api::PaymentsStartRequest> f currency = payment_attempt.currency.get_required_value("currency")?; amount = payment_attempt.amount.into(); - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.shipping_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), mechant_key_store, + &payment_intent.payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, None, payment_intent.billing_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), mechant_key_store, + &payment_intent.payment_id, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 6d3c4d776a..a29d1c14d5 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -241,12 +241,16 @@ async fn get_tracker_for_sync< db, payment_intent.shipping_address_id.clone(), mechant_key_store, + payment_intent.payment_id.clone(), + merchant_account.merchant_id.clone(), ) .await?; let billing_address = helpers::get_address_by_id( db, payment_intent.billing_address_id.clone(), mechant_key_store, + payment_intent.payment_id.clone(), + merchant_account.merchant_id.clone(), ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 647979a118..318c4e8ad7 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -146,7 +146,7 @@ impl GetTracker, api::PaymentsRequest> for Pa )?; } - let shipping_address = helpers::get_address_for_payment_request( + let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, request.shipping.as_ref(), payment_intent.shipping_address_id.as_deref(), @@ -156,9 +156,10 @@ impl GetTracker, api::PaymentsRequest> for Pa .as_ref() .or(customer_details.customer_id.as_ref()), key_store, + &payment_intent.payment_id, ) .await?; - let billing_address = helpers::get_address_for_payment_request( + let billing_address = helpers::create_or_find_address_for_payment_by_request( db, request.billing.as_ref(), payment_intent.billing_address_id.as_deref(), @@ -168,6 +169,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .as_ref() .or(customer_details.customer_id.as_ref()), key_store, + &payment_intent.payment_id, ) .await?; diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index ed293383f3..a640478d8d 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -1158,13 +1158,14 @@ pub async fn payout_create_db_entries( .customer_id; // Get or create address - let billing_address = payment_helpers::get_address_for_payment_request( + let billing_address = payment_helpers::create_or_find_address_for_payment_by_request( db, req.billing.as_ref(), None, merchant_id, Some(&customer_id.to_owned()), key_store, + payout_id, ) .await?; let address_id = billing_address @@ -1293,13 +1294,14 @@ pub async fn make_payout_data( .await .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?; - let billing_address = payment_helpers::get_address_for_payment_request( + let billing_address = payment_helpers::create_or_find_address_for_payment_by_request( db, None, Some(&payouts.address_id.to_owned()), merchant_id, Some(&payouts.customer_id.to_owned()), key_store, + &payouts.payout_id, ) .await?; diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 90eef962c9..f750b645d6 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -297,6 +297,7 @@ pub async fn get_or_create_customer_details( id: None, created_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(), + address_id: None, }; Ok(Some( diff --git a/crates/router/src/db/address.rs b/crates/router/src/db/address.rs index e73ee85dd3..faea880f9e 100644 --- a/crates/router/src/db/address.rs +++ b/crates/router/src/db/address.rs @@ -28,14 +28,23 @@ where key_store: &domain::MerchantKeyStore, ) -> CustomResult; - async fn insert_address( + async fn insert_address_for_payments( + &self, + payment_id: &str, + address: domain::Address, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult; + + async fn insert_address_for_customers( &self, address: domain::Address, key_store: &domain::MerchantKeyStore, ) -> CustomResult; - async fn find_address( + async fn find_address_by_merchant_id_payment_id_address_id( &self, + merchant_id: &str, + payment_id: &str, address_id: &str, key_store: &domain::MerchantKeyStore, ) -> CustomResult; @@ -51,23 +60,30 @@ where #[async_trait::async_trait] impl AddressInterface for Store { - async fn find_address( + async fn find_address_by_merchant_id_payment_id_address_id( &self, + merchant_id: &str, + payment_id: &str, address_id: &str, key_store: &domain::MerchantKeyStore, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - storage::Address::find_by_address_id(&conn, address_id) - .await - .map_err(Into::into) - .into_report() - .async_and_then(|address| async { - address - .convert(key_store.key.get_inner()) - .await - .change_context(errors::StorageError::DecryptionError) - }) - .await + storage::Address::find_by_merchant_id_payment_id_address_id( + &conn, + merchant_id, + payment_id, + address_id, + ) + .await + .map_err(Into::into) + .into_report() + .async_and_then(|address| async { + address + .convert(key_store.key.get_inner()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } #[instrument(skip_all)] @@ -91,8 +107,31 @@ impl AddressInterface for Store { .await } - #[instrument(skip_all)] - async fn insert_address( + async fn insert_address_for_payments( + &self, + _payment_id: &str, + address: domain::Address, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + address + .construct_new() + .await + .change_context(errors::StorageError::EncryptionError)? + .insert(&conn) + .await + .map_err(Into::into) + .into_report() + .async_and_then(|address| async { + address + .convert(key_store.key.get_inner()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await + } + + async fn insert_address_for_customers( &self, address: domain::Address, key_store: &domain::MerchantKeyStore, @@ -150,8 +189,10 @@ impl AddressInterface for Store { #[async_trait::async_trait] impl AddressInterface for MockDb { - async fn find_address( + async fn find_address_by_merchant_id_payment_id_address_id( &self, + _merchant_id: &str, + _payment_id: &str, address_id: &str, key_store: &domain::MerchantKeyStore, ) -> CustomResult { @@ -205,8 +246,27 @@ impl AddressInterface for MockDb { } } - #[instrument(skip_all)] - async fn insert_address( + async fn insert_address_for_payments( + &self, + _payment_id: &str, + address_new: domain::Address, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + let mut addresses = self.addresses.lock().await; + + let address = Conversion::convert(address_new) + .await + .change_context(errors::StorageError::EncryptionError)?; + + addresses.push(address.clone()); + + address + .convert(key_store.key.get_inner()) + .await + .change_context(errors::StorageError::DecryptionError) + } + + async fn insert_address_for_customers( &self, address_new: domain::Address, key_store: &domain::MerchantKeyStore, diff --git a/crates/router/src/types/domain/address.rs b/crates/router/src/types/domain/address.rs index d80ac45783..fadd83b34e 100644 --- a/crates/router/src/types/domain/address.rs +++ b/crates/router/src/types/domain/address.rs @@ -17,7 +17,6 @@ use super::{ pub struct Address { #[serde(skip_serializing)] pub id: Option, - #[serde(skip_serializing)] pub address_id: String, pub city: Option, pub country: Option, @@ -38,6 +37,7 @@ pub struct Address { pub modified_at: PrimitiveDateTime, pub customer_id: String, pub merchant_id: String, + pub payment_id: Option, } #[async_trait] @@ -47,9 +47,7 @@ impl behaviour::Conversion for Address { async fn convert(self) -> CustomResult { Ok(diesel_models::address::Address { - id: self.id.ok_or(ValidationError::MissingRequiredField { - field_name: "id".to_string(), - })?, + id: self.id, address_id: self.address_id, city: self.city, country: self.country, @@ -66,6 +64,7 @@ impl behaviour::Conversion for Address { modified_at: self.modified_at, customer_id: self.customer_id, merchant_id: self.merchant_id, + payment_id: self.payment_id, }) } @@ -76,7 +75,7 @@ impl behaviour::Conversion for Address { async { let inner_decrypt = |inner| types::decrypt(inner, key.peek()); Ok(Self { - id: Some(other.id), + id: other.id, address_id: other.address_id, city: other.city, country: other.country, @@ -93,6 +92,7 @@ impl behaviour::Conversion for Address { modified_at: other.modified_at, customer_id: other.customer_id, merchant_id: other.merchant_id, + payment_id: other.payment_id, }) } .await @@ -102,11 +102,6 @@ impl behaviour::Conversion for Address { } async fn construct_new(self) -> CustomResult { - common_utils::fp_utils::when(self.id.is_some(), || { - Err(ValidationError::InvalidValue { - message: "id present while creating a new database entry".to_string(), - }) - })?; let now = date_time::now(); Ok(Self::NewDstType { address_id: self.address_id, @@ -123,6 +118,7 @@ impl behaviour::Conversion for Address { country_code: self.country_code, customer_id: self.customer_id, merchant_id: self.merchant_id, + payment_id: self.payment_id, created_at: now, modified_at: now, }) diff --git a/crates/router/src/types/domain/customer.rs b/crates/router/src/types/domain/customer.rs index 2d4272adb6..3810523b41 100644 --- a/crates/router/src/types/domain/customer.rs +++ b/crates/router/src/types/domain/customer.rs @@ -21,6 +21,7 @@ pub struct Customer { pub metadata: Option, pub modified_at: PrimitiveDateTime, pub connector_customer: Option, + pub address_id: Option, } #[async_trait::async_trait] @@ -43,6 +44,7 @@ impl super::behaviour::Conversion for Customer { metadata: self.metadata, modified_at: self.modified_at, connector_customer: self.connector_customer, + address_id: self.address_id, }) } @@ -69,6 +71,7 @@ impl super::behaviour::Conversion for Customer { metadata: item.metadata, modified_at: item.modified_at, connector_customer: item.connector_customer, + address_id: item.address_id, }) } .await @@ -91,6 +94,7 @@ impl super::behaviour::Conversion for Customer { created_at: now, modified_at: now, connector_customer: self.connector_customer, + address_id: self.address_id, }) } } @@ -100,11 +104,12 @@ pub enum CustomerUpdate { Update { name: crypto::OptionalEncryptableName, email: crypto::OptionalEncryptableEmail, - phone: crypto::OptionalEncryptablePhone, + phone: Box, description: Option, phone_country_code: Option, metadata: Option, connector_customer: Option, + address_id: Option, }, ConnectorCustomer { connector_customer: Option, @@ -122,6 +127,7 @@ impl From for CustomerUpdateInternal { phone_country_code, metadata, connector_customer, + address_id, } => Self { name: name.map(Encryption::from), email: email.map(Encryption::from), @@ -131,6 +137,7 @@ impl From for CustomerUpdateInternal { metadata, connector_customer, modified_at: Some(date_time::now()), + address_id, }, CustomerUpdate::ConnectorCustomer { connector_customer } => Self { connector_customer, diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 94571bb1fc..27c8acb1fa 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -32,7 +32,14 @@ use crate::{ db::StorageInterface, logger, routes::metrics, - types::{self, domain}, + types::{ + self, + domain::{ + self, + types::{encrypt_optional, AsyncLift}, + }, + storage, + }, }; pub mod error_parser { @@ -397,3 +404,128 @@ pub fn add_connector_http_status_code_metrics(option_status_code: Option) { logger::info!("Skip metrics as no http status code received from connector") } } + +#[async_trait::async_trait] +pub trait CustomerAddress { + async fn get_address_update( + &self, + address_details: api_models::payments::AddressDetails, + key: &[u8], + ) -> CustomResult; + + async fn get_domain_address( + &self, + address_details: api_models::payments::AddressDetails, + merchant_id: &str, + customer_id: &str, + key: &[u8], + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl CustomerAddress for api_models::customers::CustomerRequest { + async fn get_address_update( + &self, + address_details: api_models::payments::AddressDetails, + key: &[u8], + ) -> CustomResult { + async { + Ok(storage::AddressUpdate::Update { + city: address_details.city, + country: address_details.country, + line1: address_details + .line1 + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + line2: address_details + .line2 + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + line3: address_details + .line3 + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + zip: address_details + .zip + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + state: address_details + .state + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + first_name: address_details + .first_name + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + last_name: address_details + .last_name + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + phone_number: self + .phone + .clone() + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + country_code: self.phone_country_code.clone(), + }) + } + .await + } + + async fn get_domain_address( + &self, + address_details: api_models::payments::AddressDetails, + merchant_id: &str, + customer_id: &str, + key: &[u8], + ) -> CustomResult { + async { + Ok(domain::Address { + id: None, + city: address_details.city, + country: address_details.country, + line1: address_details + .line1 + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + line2: address_details + .line2 + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + line3: address_details + .line3 + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + zip: address_details + .zip + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + state: address_details + .state + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + first_name: address_details + .first_name + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + last_name: address_details + .last_name + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + phone_number: self + .phone + .clone() + .async_lift(|inner| encrypt_optional(inner, key)) + .await?, + country_code: self.phone_country_code.clone(), + customer_id: customer_id.to_string(), + merchant_id: merchant_id.to_string(), + address_id: generate_id(consts::ID_LENGTH, "add"), + payment_id: None, + created_at: common_utils::date_time::now(), + modified_at: common_utils::date_time::now(), + }) + } + .await + } +} diff --git a/migrations/2023-09-14-032447_add_payment_id_in_address/down.sql b/migrations/2023-09-14-032447_add_payment_id_in_address/down.sql new file mode 100644 index 0000000000..257426014b --- /dev/null +++ b/migrations/2023-09-14-032447_add_payment_id_in_address/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE address DROP COLUMN payment_id; +ALTER TABLE customers DROP COLUMN address_id; \ No newline at end of file diff --git a/migrations/2023-09-14-032447_add_payment_id_in_address/up.sql b/migrations/2023-09-14-032447_add_payment_id_in_address/up.sql new file mode 100644 index 0000000000..af3503b057 --- /dev/null +++ b/migrations/2023-09-14-032447_add_payment_id_in_address/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE address ADD COLUMN payment_id VARCHAR(64); +ALTER TABLE customers ADD COLUMN address_id VARCHAR(64); \ No newline at end of file diff --git a/migrations/2023-09-17-152010_make_id_not_null_address/down.sql b/migrations/2023-09-17-152010_make_id_not_null_address/down.sql new file mode 100644 index 0000000000..10a050ce12 --- /dev/null +++ b/migrations/2023-09-17-152010_make_id_not_null_address/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE address ALTER COLUMN id SET NOT NULL; \ No newline at end of file diff --git a/migrations/2023-09-17-152010_make_id_not_null_address/up.sql b/migrations/2023-09-17-152010_make_id_not_null_address/up.sql new file mode 100644 index 0000000000..e32bb872fe --- /dev/null +++ b/migrations/2023-09-17-152010_make_id_not_null_address/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE address ALTER COLUMN id DROP NOT NULL; \ No newline at end of file