feat(address): add email field to address (#3682)

This commit is contained in:
Narayan Bhat
2024-02-22 16:18:27 +05:30
committed by GitHub
parent d000847b93
commit 863e380cf2
39 changed files with 112 additions and 5 deletions

View File

@ -1996,6 +1996,9 @@ pub struct Address {
pub address: Option<AddressDetails>,
pub phone: Option<PhoneDetails>,
#[schema(value_type = Option<String>)]
pub email: Option<Email>,
}
// used by customers also, could be moved outside

View File

@ -25,6 +25,7 @@ pub struct AddressNew {
pub created_at: PrimitiveDateTime,
pub modified_at: PrimitiveDateTime,
pub updated_by: String,
pub email: Option<Encryption>,
}
#[derive(Clone, Debug, Queryable, Identifiable, Serialize, Deserialize)]
@ -49,6 +50,7 @@ pub struct Address {
pub merchant_id: String,
pub payment_id: Option<String>,
pub updated_by: String,
pub email: Option<Encryption>,
}
#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay, Serialize, Deserialize)]
@ -67,6 +69,7 @@ pub struct AddressUpdateInternal {
pub country_code: Option<String>,
pub modified_at: PrimitiveDateTime,
pub updated_by: String,
pub email: Option<Encryption>,
}
impl AddressUpdateInternal {

View File

@ -31,6 +31,7 @@ diesel::table! {
payment_id -> Nullable<Varchar>,
#[max_length = 32]
updated_by -> Varchar,
email -> Nullable<Bytea>,
}
}

View File

@ -39,6 +39,7 @@ impl From<StripeBillingDetails> for payments::Address {
address.country.as_ref().map(|country| country.to_string())
}),
}),
email: details.email,
address: details.address.map(|address| payments::AddressDetails {
city: address.city,
country: address.country,
@ -184,6 +185,7 @@ impl From<Shipping> for payments::Address {
number: details.phone,
country_code: details.address.country.map(|country| country.to_string()),
}),
email: None,
address: Some(payments::AddressDetails {
city: details.address.city,
country: details.address.country,

View File

@ -37,6 +37,7 @@ impl From<StripeBillingDetails> for payments::Address {
number: details.phone,
country_code: None,
}),
email: details.email,
}
}
}
@ -143,6 +144,7 @@ impl From<Shipping> for payments::Address {
number: details.phone,
country_code: None,
}),
email: None,
}
}
}

View File

@ -249,6 +249,12 @@ pub async fn delete_customer(
.await
.switch()?;
let redacted_encrypted_email: Encryptable<
masking::Secret<_, common_utils::pii::EmailStrategy>,
> = Encryptable::encrypt(REDACTED.to_string().into(), key, GcmAes256)
.await
.switch()?;
let update_address = storage::AddressUpdate::Update {
city: Some(REDACTED.to_string()),
country: None,
@ -262,6 +268,7 @@ pub async fn delete_customer(
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

View File

@ -69,7 +69,7 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
.as_ref()
.and_then(|mandate_data| mandate_data.update_mandate_id.clone())
{
self.update_mandate_flow(
Box::pin(self.update_mandate_flow(
state,
merchant_account,
mandate_id,
@ -79,7 +79,7 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
&state.conf.mandates.update_mandate_supported,
connector_request,
maybe_customer,
)
))
.await
} else {
let connector_integration: services::BoxedConnectorIntegration<

View File

@ -181,6 +181,14 @@ pub async fn create_or_update_address_for_payment_by_request(
.as_ref()
.and_then(|value| value.country_code.clone()),
updated_by: storage_scheme.to_string(),
email: address
.email
.as_ref()
.cloned()
.async_lift(|inner| {
types::encrypt_optional(inner.map(|inner| inner.expose()), key)
})
.await?,
})
}
.await
@ -370,6 +378,12 @@ pub async fn get_domain_address_for_payments(
.await?,
payment_id: Some(payment_id.to_owned()),
updated_by: storage_scheme.to_string(),
email: address
.email
.as_ref()
.cloned()
.async_lift(|inner| types::encrypt_optional(inner.map(|inner| inner.expose()), key))
.await?,
})
}
.await

View File

@ -2,7 +2,7 @@ pub mod helpers;
pub mod validator;
use api_models::enums as api_enums;
use common_utils::{crypto::Encryptable, ext_traits::ValueExt};
use common_utils::{crypto::Encryptable, ext_traits::ValueExt, pii};
use diesel_models::enums as storage_enums;
use error_stack::{report, ResultExt};
use router_env::{instrument, tracing};
@ -1096,6 +1096,7 @@ pub async fn response_handler(
api::payments::Address {
phone: Some(phone_details),
address: Some(address_details),
email: a.email.to_owned().map(pii::Email::from),
}
});

View File

@ -108,6 +108,7 @@ pub async fn construct_payout_router_data<'a, F>(
api_models::payments::Address {
phone: Some(phone_details),
address: Some(address_details),
email: a.email.to_owned().map(Email::from),
}
}),
};

View File

@ -501,6 +501,7 @@ mod storage {
merchant_id: address_new.merchant_id.clone(),
payment_id: address_new.payment_id.clone(),
updated_by: storage_scheme.to_string(),
email: address_new.email.clone(),
};
let redis_entry = kv::TypedSql {

View File

@ -39,6 +39,7 @@ pub struct Address {
pub merchant_id: String,
pub payment_id: Option<String>,
pub updated_by: String,
pub email: crypto::OptionalEncryptableEmail,
}
#[async_trait]
@ -67,6 +68,7 @@ impl behaviour::Conversion for Address {
merchant_id: self.merchant_id,
payment_id: self.payment_id,
updated_by: self.updated_by,
email: self.email.map(Encryption::from),
})
}
@ -76,6 +78,7 @@ impl behaviour::Conversion for Address {
) -> CustomResult<Self, ValidationError> {
async {
let inner_decrypt = |inner| types::decrypt(inner, key.peek());
let inner_decrypt_email = |inner| types::decrypt(inner, key.peek());
Ok(Self {
id: other.id,
address_id: other.address_id,
@ -96,6 +99,7 @@ impl behaviour::Conversion for Address {
merchant_id: other.merchant_id,
payment_id: other.payment_id,
updated_by: other.updated_by,
email: other.email.async_lift(inner_decrypt_email).await?,
})
}
.await
@ -125,6 +129,7 @@ impl behaviour::Conversion for Address {
created_at: now,
modified_at: now,
updated_by: self.updated_by,
email: self.email.map(Encryption::from),
})
}
}
@ -144,6 +149,7 @@ pub enum AddressUpdate {
phone_number: crypto::OptionalEncryptableSecretString,
country_code: Option<String>,
updated_by: String,
email: crypto::OptionalEncryptableEmail,
},
}
@ -163,6 +169,7 @@ impl From<AddressUpdate> for AddressUpdateInternal {
phone_number,
country_code,
updated_by,
email,
} => Self {
city,
country,
@ -177,6 +184,7 @@ impl From<AddressUpdate> for AddressUpdateInternal {
country_code,
modified_at: date_time::convert_to_pdt(OffsetDateTime::now_utc()),
updated_by,
email: email.map(Encryption::from),
},
}
}

View File

@ -589,6 +589,7 @@ impl<'a> From<&'a domain::Address> for api_types::Address {
number: address.phone_number.clone().map(Encryptable::into_inner),
country_code: address.country_code.clone(),
}),
email: address.email.clone().map(pii::Email::from),
}
}
}

View File

@ -26,6 +26,7 @@ pub use common_utils::{
use data_models::payments::PaymentIntent;
use error_stack::{IntoReport, ResultExt};
use image::Luma;
use masking::ExposeInterface;
use nanoid::nanoid;
use qrcode;
use serde::de::DeserializeOwned;
@ -564,6 +565,12 @@ impl CustomerAddress for api_models::customers::CustomerRequest {
.await?,
country_code: self.phone_country_code.clone(),
updated_by: storage_scheme.to_string(),
email: self
.email
.as_ref()
.cloned()
.async_lift(|inner| encrypt_optional(inner.map(|inner| inner.expose()), key))
.await?,
})
}
.await
@ -623,6 +630,12 @@ impl CustomerAddress for api_models::customers::CustomerRequest {
created_at: common_utils::date_time::now(),
modified_at: common_utils::date_time::now(),
updated_by: storage_scheme.to_string(),
email: self
.email
.as_ref()
.cloned()
.async_lift(|inner| encrypt_optional(inner.map(|inner| inner.expose()), key))
.await?,
})
}
.await

View File

@ -62,6 +62,7 @@ impl AdyenTest {
..Default::default()
}),
phone: None,
email: None,
}),
..Default::default()
}),
@ -88,6 +89,7 @@ impl AdyenTest {
..Default::default()
}),
phone: None,
email: None,
}),
..Default::default()
}),

View File

@ -55,6 +55,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
number: Some(Secret::new("1234567890".to_string())),
country_code: Some("+91".to_string()),
}),
email: None,
}),
..Default::default()
}),

View File

@ -54,6 +54,7 @@ fn get_payment_info() -> Option<PaymentInfo> {
..Default::default()
}),
phone: None,
email: None,
}),
..Default::default()
}),

View File

@ -82,6 +82,7 @@ impl CashtocodeTest {
..Default::default()
}),
phone: None,
email: None,
}),
..Default::default()
}),

View File

@ -56,6 +56,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
number: Some(Secret::new("1234567890".to_string())),
country_code: Some("+91".to_string()),
}),
email: None,
}),
..Default::default()
}),

View File

@ -55,6 +55,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
number: Some(Secret::new("1234567890".to_string())),
country_code: Some("+91".to_string()),
}),
email: None,
}),
..Default::default()
}),

View File

@ -54,6 +54,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
number: Some(Secret::new("1234567890".to_string())),
country_code: Some("+91".to_string()),
}),
email: None,
}),
..Default::default()
}),

View File

@ -435,6 +435,7 @@ pub fn get_payment_info() -> PaymentInfo {
first_name: None,
last_name: None,
}),
email: None,
}),
}),
auth_type: None,

View File

@ -67,6 +67,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
number: Some(Secret::new("1234567890".to_string())),
country_code: Some("+91".to_string()),
}),
email: None,
}),
..Default::default()
}),

View File

@ -64,6 +64,7 @@ impl Globalpay {
..Default::default()
}),
phone: None,
..Default::default()
}),
..Default::default()
}),

View File

@ -71,6 +71,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
number: Some(Secret::new("1234567890".to_string())),
country_code: Some("+91".to_string()),
}),
email: None,
}),
..Default::default()
}),

View File

@ -53,6 +53,7 @@ fn get_default_payment_info() -> Option<PaymentInfo> {
state: Some(Secret::new("Amsterdam".to_string())),
}),
phone: None,
email: None,
}),
});
Some(PaymentInfo {

View File

@ -55,6 +55,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
number: Some(Secret::new("1234567890".to_string())),
country_code: Some("+91".to_string()),
}),
email: None,
}),
..Default::default()
}),

View File

@ -62,6 +62,7 @@ impl PayeezyTest {
..Default::default()
}),
phone: None,
email: None,
}),
..Default::default()
}),

View File

@ -57,6 +57,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
last_name: Some(Secret::new("Doe".to_string())),
}),
phone: None,
email: None,
}),
}),
auth_type: None,

View File

@ -80,6 +80,7 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
..Default::default()
}),
phone: None,
email: None,
}),
..Default::default()
}),

View File

@ -66,6 +66,7 @@ impl WiseTest {
..Default::default()
}),
phone: None,
email: None,
}),
..Default::default()
}),

View File

@ -50,6 +50,7 @@ impl WorldlineTest {
..Default::default()
}),
phone: None,
email: None,
}),
..Default::default()
}),

View File

@ -333,10 +333,12 @@ async fn payments_create_core() {
shipping: Some(api::Address {
address: None,
phone: None,
email: None,
}),
billing: Some(api::Address {
address: None,
phone: None,
email: None,
}),
statement_descriptor_name: Some("Hyperswtich".to_string()),
statement_descriptor_suffix: Some("Hyperswitch".to_string()),
@ -509,10 +511,12 @@ async fn payments_create_core_adyen_no_redirect() {
shipping: Some(api::Address {
address: None,
phone: None,
email: None,
}),
billing: Some(api::Address {
address: None,
phone: None,
email: None,
}),
statement_descriptor_name: Some("Juspay".to_string()),
statement_descriptor_suffix: Some("Router".to_string()),

View File

@ -93,10 +93,12 @@ async fn payments_create_core() {
shipping: Some(api::Address {
address: None,
phone: None,
email: None,
}),
billing: Some(api::Address {
address: None,
phone: None,
email: None,
}),
statement_descriptor_name: Some("Hyperswitch".to_string()),
statement_descriptor_suffix: Some("Hyperswitch".to_string()),
@ -273,9 +275,15 @@ async fn payments_create_core_adyen_no_redirect() {
nick_name: Some(masking::Secret::new("nick_name".into())),
})),
payment_method: Some(api_enums::PaymentMethod::Card),
shipping: Some(api::Address {
address: None,
phone: None,
email: None,
}),
billing: Some(api::Address {
address: None,
phone: None,
email: None,
}),
statement_descriptor_name: Some("Juspay".to_string()),
statement_descriptor_suffix: Some("Router".to_string()),

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE address DROP COLUMN IF EXISTS email;

View File

@ -0,0 +1,3 @@
-- Your SQL goes here
ALTER TABLE address
ADD COLUMN IF NOT EXISTS email BYTEA;

View File

@ -4315,6 +4315,10 @@
}
],
"nullable": true
},
"email": {
"type": "string",
"nullable": true
}
}
},

View File

@ -19,7 +19,7 @@ pm.test("[POST]::/payments - Response has JSON Body", function () {
let jsonData = {};
try {
jsonData = pm.response.json();
} catch (e) {}
} catch (e) { }
// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id
if (jsonData?.payment_id) {
@ -88,3 +88,12 @@ pm.test(
.true;
},
);
// Response body should have "billing.email"
pm.test(
"[POST]::/payments - Content check if 'billing.email' exists",
function () {
pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be
.true;
},
);

View File

@ -58,7 +58,8 @@
"phone": {
"number": "8056594427",
"country_code": "+91"
}
},
"email": "example@example.com"
},
"shipping": {
"address": {