refactor(router): refactor customer <> address in customers and payments flow (#2158)

This commit is contained in:
Sai Harsha Vardhan
2023-09-20 19:27:45 +05:30
committed by GitHub
parent 809832213e
commit 8ee2ce1f4f
28 changed files with 526 additions and 333 deletions

View File

@ -20,14 +20,15 @@ pub struct AddressNew {
pub country_code: Option<String>,
pub customer_id: String,
pub merchant_id: String,
pub payment_id: Option<String>,
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<i32>,
pub address_id: String,
pub city: Option<String>,
pub country: Option<enums::CountryAlpha2>,
@ -44,6 +45,7 @@ pub struct Address {
pub modified_at: PrimitiveDateTime,
pub customer_id: String,
pub merchant_id: String,
pub payment_id: Option<String>,
}
#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]

View File

@ -18,6 +18,7 @@ pub struct CustomerNew {
pub connector_customer: Option<serde_json::Value>,
pub created_at: PrimitiveDateTime,
pub modified_at: PrimitiveDateTime,
pub address_id: Option<String>,
}
#[derive(Clone, Debug, Identifiable, Queryable)]
@ -35,6 +36,7 @@ pub struct Customer {
pub metadata: Option<pii::SecretSerdeValue>,
pub connector_customer: Option<serde_json::Value>,
pub modified_at: PrimitiveDateTime,
pub address_id: Option<String>,
}
#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
@ -48,4 +50,5 @@ pub struct CustomerUpdateInternal {
pub metadata: Option<pii::SecretSerdeValue>,
pub modified_at: Option<PrimitiveDateTime>,
pub connector_customer: Option<serde_json::Value>,
pub address_id: Option<String>,
}

View File

@ -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<Self> {
generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(conn, address_id.to_owned())
.await
match generics::generic_find_one::<<Self as HasTable>::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::<<Self as HasTable>::Table, _, _>(
conn,
address_id.to_owned(),
)
.await
}
_ => Err(error),
},
result => result,
}
}
#[instrument(skip(conn))]

View File

@ -5,7 +5,7 @@ diesel::table! {
use crate::enums::diesel_exports::*;
address (address_id) {
id -> Int4,
id -> Nullable<Int4>,
#[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<Varchar>,
}
}
@ -197,6 +199,8 @@ diesel::table! {
metadata -> Nullable<Json>,
connector_customer -> Nullable<Jsonb>,
modified_at -> Timestamp,
#[max_length = 64]
address_id -> Nullable<Varchar>,
}
}

View File

@ -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<customers::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

View File

@ -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()?

View File

@ -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<Option<domain::Address>, 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<domain::Address, common_utils::errors::CryptoError> {
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<String>,
merchant_key_store: &domain::MerchantKeyStore,
payment_id: String,
merchant_id: String,
) -> CustomResult<Option<domain::Address>, 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

View File

@ -136,22 +136,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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?;

View File

@ -79,23 +79,25 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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?;

View File

@ -153,23 +153,25 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, 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?;

View File

@ -145,22 +145,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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?;

View File

@ -104,7 +104,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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<F: Send + Clone> GetTracker<F, PaymentData<F>, 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<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
.as_ref()
.or(customer_details.customer_id.as_ref()),
key_store,
&payment_intent.payment_id,
);
let config_update_fut = request

View File

@ -92,23 +92,25 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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?;

View File

@ -77,23 +77,25 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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?;

View File

@ -90,23 +90,25 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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?;

View File

@ -85,22 +85,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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?;

View File

@ -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?;

View File

@ -146,7 +146,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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<F: Send + Clone> GetTracker<F, PaymentData<F>, 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<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
.as_ref()
.or(customer_details.customer_id.as_ref()),
key_store,
&payment_intent.payment_id,
)
.await?;

View File

@ -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?;

View File

@ -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(

View File

@ -28,14 +28,23 @@ where
key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Address, errors::StorageError>;
async fn insert_address(
async fn insert_address_for_payments(
&self,
payment_id: &str,
address: domain::Address,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Address, errors::StorageError>;
async fn insert_address_for_customers(
&self,
address: domain::Address,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Address, errors::StorageError>;
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<domain::Address, errors::StorageError>;
@ -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<domain::Address, errors::StorageError> {
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<domain::Address, errors::StorageError> {
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<domain::Address, errors::StorageError> {
@ -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<domain::Address, errors::StorageError> {
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,

View File

@ -17,7 +17,6 @@ use super::{
pub struct Address {
#[serde(skip_serializing)]
pub id: Option<i32>,
#[serde(skip_serializing)]
pub address_id: String,
pub city: Option<String>,
pub country: Option<enums::CountryAlpha2>,
@ -38,6 +37,7 @@ pub struct Address {
pub modified_at: PrimitiveDateTime,
pub customer_id: String,
pub merchant_id: String,
pub payment_id: Option<String>,
}
#[async_trait]
@ -47,9 +47,7 @@ impl behaviour::Conversion for Address {
async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
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<Self::NewDstType, ValidationError> {
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,
})

View File

@ -21,6 +21,7 @@ pub struct Customer {
pub metadata: Option<pii::SecretSerdeValue>,
pub modified_at: PrimitiveDateTime,
pub connector_customer: Option<serde_json::Value>,
pub address_id: Option<String>,
}
#[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<crypto::OptionalEncryptablePhone>,
description: Option<String>,
phone_country_code: Option<String>,
metadata: Option<pii::SecretSerdeValue>,
connector_customer: Option<serde_json::Value>,
address_id: Option<String>,
},
ConnectorCustomer {
connector_customer: Option<serde_json::Value>,
@ -122,6 +127,7 @@ impl From<CustomerUpdate> 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<CustomerUpdate> for CustomerUpdateInternal {
metadata,
connector_customer,
modified_at: Some(date_time::now()),
address_id,
},
CustomerUpdate::ConnectorCustomer { connector_customer } => Self {
connector_customer,

View File

@ -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<u16>) {
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<storage::AddressUpdate, common_utils::errors::CryptoError>;
async fn get_domain_address(
&self,
address_details: api_models::payments::AddressDetails,
merchant_id: &str,
customer_id: &str,
key: &[u8],
) -> CustomResult<domain::Address, common_utils::errors::CryptoError>;
}
#[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<storage::AddressUpdate, common_utils::errors::CryptoError> {
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<domain::Address, common_utils::errors::CryptoError> {
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
}
}

View File

@ -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;

View File

@ -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);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE address ALTER COLUMN id SET NOT NULL;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE address ALTER COLUMN id DROP NOT NULL;