refactor(billing): store payment_method_data_billing for recurring payments (#4513)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Narayan Bhat
2024-05-09 18:42:05 +05:30
committed by GitHub
parent e70d58afc9
commit 55ae0fc5f7
18 changed files with 333 additions and 152 deletions

View File

@@ -951,6 +951,10 @@ pub struct CustomerPaymentMethod {
/// Indicates if the payment method has been set to default or not
#[schema(example = true)]
pub default_payment_method_set: bool,
/// The billing details of the payment method
#[schema(value_type = Option<Address>)]
pub billing: Option<payments::Address>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]

View File

@@ -40,6 +40,7 @@ pub struct PaymentMethod {
pub status: storage_enums::PaymentMethodStatus,
pub network_transaction_id: Option<String>,
pub client_secret: Option<String>,
pub payment_method_billing_address: Option<Encryption>,
}
#[derive(
@@ -75,43 +76,7 @@ pub struct PaymentMethodNew {
pub status: storage_enums::PaymentMethodStatus,
pub network_transaction_id: Option<String>,
pub client_secret: Option<String>,
}
impl Default for PaymentMethodNew {
fn default() -> Self {
let now = common_utils::date_time::now();
Self {
customer_id: String::default(),
merchant_id: String::default(),
payment_method_id: String::default(),
locker_id: Option::default(),
payment_method: Option::default(),
payment_method_type: Option::default(),
payment_method_issuer: Option::default(),
payment_method_issuer_code: Option::default(),
accepted_currency: Option::default(),
scheme: Option::default(),
token: Option::default(),
cardholder_name: Option::default(),
issuer_name: Option::default(),
issuer_country: Option::default(),
payer_country: Option::default(),
is_stored: Option::default(),
swift_code: Option::default(),
direct_debit_token: Option::default(),
created_at: now,
last_modified: now,
metadata: Option::default(),
payment_method_data: Option::default(),
last_used_at: now,
connector_mandate_details: Option::default(),
customer_acceptance: Option::default(),
status: storage_enums::PaymentMethodStatus::Active,
network_transaction_id: Option::default(),
client_secret: Option::default(),
}
}
pub payment_method_billing_address: Option<Encryption>,
}
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
@@ -337,6 +302,9 @@ impl From<&PaymentMethodNew> for PaymentMethod {
status: payment_method_new.status,
network_transaction_id: payment_method_new.network_transaction_id.clone(),
client_secret: payment_method_new.client_secret.clone(),
payment_method_billing_address: payment_method_new
.payment_method_billing_address
.clone(),
}
}
}

View File

@@ -926,6 +926,7 @@ diesel::table! {
network_transaction_id -> Nullable<Varchar>,
#[max_length = 128]
client_secret -> Nullable<Varchar>,
payment_method_billing_address -> Nullable<Bytea>,
}
}

View File

@@ -1,5 +1,6 @@
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
str::FromStr,
};
@@ -88,6 +89,7 @@ pub async fn create_payment_method(
status: Option<enums::PaymentMethodStatus>,
network_transaction_id: Option<String>,
storage_scheme: MerchantStorageScheme,
payment_method_billing_address: Option<Encryption>,
) -> errors::CustomResult<storage::PaymentMethod, errors::ApiErrorResponse> {
let customer = db
.find_customer_by_customer_id_merchant_id(
@@ -104,6 +106,8 @@ pub async fn create_payment_method(
format!("{payment_method_id}_secret").as_str(),
);
let current_time = common_utils::date_time::now();
let response = db
.insert_payment_method(
storage::PaymentMethodNew {
@@ -122,7 +126,20 @@ pub async fn create_payment_method(
client_secret: Some(client_secret),
status: status.unwrap_or(enums::PaymentMethodStatus::Active),
network_transaction_id: network_transaction_id.to_owned(),
..storage::PaymentMethodNew::default()
payment_method_issuer_code: None,
accepted_currency: None,
token: None,
cardholder_name: None,
issuer_name: None,
issuer_country: None,
payer_country: None,
is_stored: None,
swift_code: None,
direct_debit_token: None,
created_at: current_time,
last_modified: current_time,
last_used_at: current_time,
payment_method_billing_address,
},
storage_scheme,
)
@@ -231,6 +248,7 @@ pub async fn get_or_insert_payment_method(
None,
None,
merchant_account.storage_scheme,
None,
)
.await
} else {
@@ -278,6 +296,7 @@ pub async fn get_client_secret_or_add_payment_method(
Some(enums::PaymentMethodStatus::AwaitingData),
None,
merchant_account.storage_scheme,
None,
)
.await?;
@@ -434,7 +453,7 @@ pub async fn add_payment_method_data(
let updated_pmd = Some(PaymentMethodsData::Card(updated_card));
let pm_data_encrypted =
create_encrypted_payment_method_data(&key_store, updated_pmd).await;
create_encrypted_data(&key_store, updated_pmd).await;
let pm_update = storage::PaymentMethodUpdate::AdditionalDataUpdate {
payment_method_data: pm_data_encrypted,
@@ -644,8 +663,7 @@ pub async fn add_payment_method(
let updated_pmd = updated_card.as_ref().map(|card| {
PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()))
});
let pm_data_encrypted =
create_encrypted_payment_method_data(key_store, updated_pmd).await;
let pm_data_encrypted = create_encrypted_data(key_store, updated_pmd).await;
let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
payment_method_data: pm_data_encrypted,
@@ -688,6 +706,7 @@ pub async fn add_payment_method(
None,
None,
merchant_account.storage_scheme,
None,
)
.await?;
@@ -712,12 +731,13 @@ pub async fn insert_payment_method(
connector_mandate_details: Option<serde_json::Value>,
network_transaction_id: Option<String>,
storage_scheme: MerchantStorageScheme,
payment_method_billing_address: Option<Encryption>,
) -> errors::RouterResult<diesel_models::PaymentMethod> {
let pm_card_details = resp
.card
.as_ref()
.map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())));
let pm_data_encrypted = create_encrypted_payment_method_data(key_store, pm_card_details).await;
let pm_data_encrypted = create_encrypted_data(key_store, pm_card_details).await;
create_payment_method(
db,
&req,
@@ -733,6 +753,7 @@ pub async fn insert_payment_method(
None,
network_transaction_id,
storage_scheme,
payment_method_billing_address,
)
.await
}
@@ -891,8 +912,7 @@ pub async fn update_customer_payment_method(
let updated_pmd = updated_card
.as_ref()
.map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())));
let pm_data_encrypted =
create_encrypted_payment_method_data(&key_store, updated_pmd).await;
let pm_data_encrypted = create_encrypted_data(&key_store, updated_pmd).await;
let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
payment_method_data: pm_data_encrypted,
@@ -3463,8 +3483,14 @@ pub async fn list_customer_payment_method(
None
};
//Need validation for enabled payment method ,querying MCA
let payment_method_billing = decrypt_generic_data::<api_models::payments::Address>(
pm.payment_method_billing_address,
key,
)
.await
.attach_printable("unable to decrypt payment method billing address details")?;
// Need validation for enabled payment method ,querying MCA
let pma = api::CustomerPaymentMethod {
payment_token: parent_payment_method_token.to_owned(),
payment_method_id: pm.payment_method_id.clone(),
@@ -3488,6 +3514,7 @@ pub async fn list_customer_payment_method(
last_used_at: Some(pm.last_used_at),
default_payment_method_set: customer.default_payment_method_id.is_some()
&& customer.default_payment_method_id == Some(pm.payment_method_id),
billing: payment_method_billing,
};
customer_pms.push(pma.to_owned());
@@ -3605,6 +3632,27 @@ pub async fn list_customer_payment_method(
Ok(services::ApplicationResponse::Json(response))
}
pub async fn decrypt_generic_data<T>(
data: Option<Encryption>,
key: &[u8],
) -> errors::RouterResult<Option<T>>
where
T: serde::de::DeserializeOwned,
{
let decrypted_data = decrypt::<serde_json::Value, masking::WithType>(data, key)
.await
.change_context(errors::StorageError::DecryptionError)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to decrypt data")?;
decrypted_data
.map(|decrypted_data| decrypted_data.into_inner().expose())
.map(|decrypted_value| decrypted_value.parse_value("generic_data"))
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to parse generic data value")
}
pub async fn get_card_details_with_locker_fallback(
pm: &payment_method::PaymentMethod,
key: &[u8],
@@ -4171,18 +4219,21 @@ pub async fn delete_payment_method(
))
}
pub async fn create_encrypted_payment_method_data(
pub async fn create_encrypted_data<T>(
key_store: &domain::MerchantKeyStore,
pm_data: Option<PaymentMethodsData>,
) -> Option<Encryption> {
data: Option<T>,
) -> Option<Encryption>
where
T: Debug + serde::Serialize,
{
let key = key_store.key.get_inner().peek();
let pm_data_encrypted: Option<Encryption> = pm_data
let encrypted_data: Option<Encryption> = data
.as_ref()
.map(Encode::encode_to_value)
.transpose()
.change_context(errors::StorageError::SerializationFailed)
.attach_printable("Unable to convert payment method data to a value")
.attach_printable("Unable to convert data to a value")
.unwrap_or_else(|err| {
logger::error!(err=?err);
None
@@ -4191,14 +4242,14 @@ pub async fn create_encrypted_payment_method_data(
.async_lift(|inner| encrypt_optional(inner, key))
.await
.change_context(errors::StorageError::EncryptionError)
.attach_printable("Unable to encrypt payment method data")
.attach_printable("Unable to encrypt data")
.unwrap_or_else(|err| {
logger::error!(err=?err);
None
})
.map(|details| details.into());
pm_data_encrypted
encrypted_data
}
pub async fn list_countries_currencies_for_connector_payment_method(

View File

@@ -214,6 +214,7 @@ pub async fn create_or_update_address_for_payment_by_request(
storage_scheme,
)
.await
.map(|payment_address| payment_address.address)
.to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?,
)
}
@@ -225,38 +226,39 @@ pub async fn create_or_update_address_for_payment_by_request(
merchant_key_store,
storage_scheme,
)
.await,
.await
.map(|payment_address| payment_address.address),
)
.transpose()
.to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?,
},
None => match req_address {
Some(address) => {
// generate a new address here
let address_details = address.address.clone().unwrap_or_default();
let address = get_domain_address(address, merchant_id, key, storage_scheme)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting address while insert")?;
let payment_address = domain::PaymentAddress {
address,
payment_id: payment_id.to_string(),
customer_id: customer_id.cloned(),
};
Some(
db.insert_address_for_payments(
payment_id,
get_domain_address_for_payments(
address_details,
address,
merchant_id,
customer_id,
payment_id,
key,
storage_scheme,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting address while insert")?,
payment_address,
merchant_key_store,
storage_scheme,
)
.await
.map(|payment_address| payment_address.address)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while inserting new address")?,
)
}
None => None,
},
})
@@ -285,34 +287,34 @@ pub async fn create_or_find_address_for_payment_by_request(
merchant_key_store,
storage_scheme,
)
.await,
.await
.map(|payment_address| payment_address.address),
)
.transpose()
.to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?,
None => match req_address {
Some(address) => {
// generate a new address here
let address = get_domain_address(address, merchant_id, key, storage_scheme)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting address while insert")?;
let payment_address = domain::PaymentAddress {
address,
payment_id: payment_id.to_string(),
customer_id: customer_id.cloned(),
};
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,
storage_scheme,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting address while insert")?,
payment_address,
merchant_key_store,
storage_scheme,
)
.await
.map(|payment_address| payment_address.address)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while inserting new address")?,
)
@@ -322,16 +324,14 @@ pub async fn create_or_find_address_for_payment_by_request(
})
}
pub async fn get_domain_address_for_payments(
address_details: api_models::payments::AddressDetails,
pub async fn get_domain_address(
address: &api_models::payments::Address,
merchant_id: &str,
customer_id: Option<&String>,
payment_id: &str,
key: &[u8],
storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<domain::Address, common_utils::errors::CryptoError> {
async {
let address_details = address.address.as_ref();
Ok(domain::Address {
id: None,
phone_number: address
@@ -341,42 +341,40 @@ pub async fn get_domain_address_for_payments(
.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.cloned(),
merchant_id: merchant_id.to_string(),
address_id: generate_id(consts::ID_LENGTH, "add"),
city: address_details.city,
country: address_details.country,
city: address_details.and_then(|address_details| address_details.city.clone()),
country: address_details.and_then(|address_details| address_details.country),
line1: address_details
.line1
.and_then(|address_details| address_details.line1.clone())
.async_lift(|inner| types::encrypt_optional(inner, key))
.await?,
line2: address_details
.line2
.and_then(|address_details| address_details.line2.clone())
.async_lift(|inner| types::encrypt_optional(inner, key))
.await?,
line3: address_details
.line3
.and_then(|address_details| address_details.line3.clone())
.async_lift(|inner| types::encrypt_optional(inner, key))
.await?,
state: address_details
.state
.and_then(|address_details| address_details.state.clone())
.async_lift(|inner| types::encrypt_optional(inner, key))
.await?,
created_at: common_utils::date_time::now(),
first_name: address_details
.first_name
.and_then(|address_details| address_details.first_name.clone())
.async_lift(|inner| types::encrypt_optional(inner, key))
.await?,
last_name: address_details
.last_name
.and_then(|address_details| address_details.last_name.clone())
.async_lift(|inner| types::encrypt_optional(inner, key))
.await?,
modified_at: common_utils::date_time::now(),
zip: address_details
.zip
.and_then(|address_details| address_details.zip.clone())
.async_lift(|inner| types::encrypt_optional(inner, key))
.await?,
payment_id: Some(payment_id.to_owned()),
updated_by: storage_scheme.to_string(),
email: address
.email
@@ -408,6 +406,7 @@ pub async fn get_address_by_id(
storage_scheme,
)
.await
.map(|payment_address| payment_address.address)
.ok()),
}
}
@@ -461,7 +460,7 @@ pub async fn get_token_pm_type_mandate_details(
None,
mandate_generic_data.recurring_mandate_payment_data,
mandate_generic_data.mandate_connector,
None,
mandate_generic_data.payment_method_info,
)
}
RecurringDetails::PaymentMethodId(payment_method_id) => {
@@ -515,7 +514,7 @@ pub async fn get_token_pm_type_mandate_details(
None,
mandate_generic_data.recurring_mandate_payment_data,
mandate_generic_data.mandate_connector,
None,
mandate_generic_data.payment_method_info,
)
} else {
(
@@ -673,7 +672,7 @@ pub async fn get_token_for_recurring_mandate(
payment_method_type: payment_method.payment_method_type,
mandate_connector: Some(mandate_connector_details),
mandate_data: None,
payment_method_info: None,
payment_method_info: Some(payment_method),
})
} else {
Ok(MandateGenericData {
@@ -687,7 +686,7 @@ pub async fn get_token_for_recurring_mandate(
payment_method_type: payment_method.payment_method_type,
mandate_connector: Some(mandate_connector_details),
mandate_data: None,
payment_method_info: None,
payment_method_info: Some(payment_method),
})
}
}

View File

@@ -471,6 +471,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
}
.in_current_span(),
);
let mandate_type = m_helpers::get_mandate_type(
request.mandate_data.clone(),
request.off_session,
@@ -560,6 +561,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
} else {
(None, payment_method_info)
};
// The operation merges mandate data from both request and payment_attempt
let setup_mandate = mandate_data.map(|mut sm| {
sm.mandate_type = payment_attempt.mandate_details.clone().or(sm.mandate_type);

View File

@@ -95,6 +95,7 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
{
let customer_id = payment_data.payment_intent.customer_id.clone();
let save_payment_data = tokenization::SavePaymentMethodData::from(resp);
let payment_method_billing_address = payment_data.address.get_payment_method_billing();
let connector_name = payment_data
.payment_attempt
@@ -125,6 +126,7 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
Some(resp.request.amount),
Some(resp.request.currency),
billing_name.clone(),
payment_method_billing_address,
business_profile,
));
@@ -178,6 +180,7 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
let currency = resp.request.currency;
let payment_method_type = resp.request.payment_method_type;
let storage_scheme = merchant_account.clone().storage_scheme;
let payment_method_billing_address = payment_method_billing_address.cloned();
logger::info!("Call to save_payment_method in locker");
let _task_handle = tokio::spawn(
@@ -196,6 +199,7 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
Some(amount),
Some(currency),
billing_name,
payment_method_billing_address.as_ref(),
&business_profile,
))
.await;
@@ -599,6 +603,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SetupMandateRequestDa
.get_payment_method_billing()
.and_then(|billing_details| billing_details.address.as_ref())
.and_then(|address| address.get_optional_full_name());
let save_payment_data = tokenization::SavePaymentMethodData::from(resp);
let customer_id = payment_data.payment_intent.customer_id.clone();
let connector_name = payment_data
@@ -625,6 +630,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SetupMandateRequestDa
resp.request.amount,
Some(resp.request.currency),
billing_name,
None,
business_profile,
))
.await?;

View File

@@ -65,6 +65,7 @@ pub async fn save_payment_method<FData>(
amount: Option<i64>,
currency: Option<storage_enums::Currency>,
billing_name: Option<masking::Secret<String>>,
payment_method_billing_address: Option<&api::Address>,
business_profile: &storage::business_profile::BusinessProfile,
) -> RouterResult<(Option<String>, Option<common_enums::PaymentMethodStatus>)>
where
@@ -209,9 +210,12 @@ where
});
let pm_data_encrypted =
payment_methods::cards::create_encrypted_payment_method_data(
payment_methods::cards::create_encrypted_data(key_store, pm_card_details).await;
let encrypted_payment_method_billing_address =
payment_methods::cards::create_encrypted_data(
key_store,
pm_card_details,
payment_method_billing_address,
)
.await;
@@ -315,6 +319,7 @@ where
None,
network_transaction_id,
merchant_account.storage_scheme,
encrypted_payment_method_billing_address,
)
.await
} else {
@@ -404,6 +409,7 @@ where
connector_mandate_details,
network_transaction_id,
merchant_account.storage_scheme,
encrypted_payment_method_billing_address,
)
.await
} else {
@@ -486,7 +492,7 @@ where
))
});
let pm_data_encrypted =
payment_methods::cards::create_encrypted_payment_method_data(
payment_methods::cards::create_encrypted_data(
key_store,
updated_pmd,
)
@@ -535,6 +541,7 @@ where
None,
network_transaction_id,
merchant_account.storage_scheme,
encrypted_payment_method_billing_address,
)
.await?;
}

View File

@@ -452,7 +452,7 @@ pub async fn save_payout_data_to_locker(
)
});
(
cards::create_encrypted_payment_method_data(key_store, Some(pm_data)).await,
cards::create_encrypted_data(key_store, Some(pm_data)).await,
payment_method,
)
} else {
@@ -495,6 +495,7 @@ pub async fn save_payout_data_to_locker(
None,
None,
merchant_account.storage_scheme,
None,
)
.await?;
}

View File

@@ -420,7 +420,7 @@ async fn store_bank_details_in_payment_methods(
let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd);
let encrypted_data =
cards::create_encrypted_payment_method_data(&key_store, Some(payment_method_data))
cards::create_encrypted_data(&key_store, Some(payment_method_data))
.await
.ok_or(ApiErrorResponse::InternalServerError)?;
let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
@@ -431,21 +431,42 @@ async fn store_bank_details_in_payment_methods(
} else {
let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd);
let encrypted_data =
cards::create_encrypted_payment_method_data(&key_store, Some(payment_method_data))
cards::create_encrypted_data(&key_store, Some(payment_method_data))
.await
.ok_or(ApiErrorResponse::InternalServerError)?;
let pm_id = generate_id(consts::ID_LENGTH, "pm");
let now = common_utils::date_time::now();
let pm_new = storage::PaymentMethodNew {
customer_id: customer_id.clone(),
merchant_id: merchant_account.merchant_id.clone(),
payment_method_id: pm_id,
payment_method: Some(enums::PaymentMethod::BankDebit),
payment_method_type: Some(creds.payment_method_type),
status: enums::PaymentMethodStatus::Active,
payment_method_issuer: None,
scheme: None,
metadata: None,
payment_method_data: Some(encrypted_data),
..storage::PaymentMethodNew::default()
payment_method_issuer_code: None,
accepted_currency: None,
token: None,
cardholder_name: None,
issuer_name: None,
issuer_country: None,
payer_country: None,
is_stored: None,
swift_code: None,
direct_debit_token: None,
created_at: now,
last_modified: now,
locker_id: None,
last_used_at: now,
connector_mandate_details: None,
customer_acceptance: None,
network_transaction_id: None,
client_secret: None,
payment_method_billing_address: None,
};
new_entries.push(pm_new);

View File

@@ -28,12 +28,12 @@ where
async fn update_address_for_payments(
&self,
this: domain::Address,
this: domain::PaymentAddress,
address: domain::AddressUpdate,
payment_id: String,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError>;
) -> CustomResult<domain::PaymentAddress, errors::StorageError>;
async fn find_address_by_address_id(
&self,
@@ -44,14 +44,14 @@ where
async fn insert_address_for_payments(
&self,
payment_id: &str,
address: domain::Address,
address: domain::PaymentAddress,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError>;
) -> CustomResult<domain::PaymentAddress, errors::StorageError>;
async fn insert_address_for_customers(
&self,
address: domain::Address,
address: domain::CustomerAddress,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Address, errors::StorageError>;
@@ -62,7 +62,7 @@ where
address_id: &str,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError>;
) -> CustomResult<domain::PaymentAddress, errors::StorageError>;
async fn update_address_by_merchant_id_customer_id(
&self,
@@ -121,7 +121,7 @@ mod storage {
address_id: &str,
key_store: &domain::MerchantKeyStore,
_storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage_types::Address::find_by_merchant_id_payment_id_address_id(
&conn,
@@ -163,12 +163,12 @@ mod storage {
#[instrument(skip_all)]
async fn update_address_for_payments(
&self,
this: domain::Address,
this: domain::PaymentAddress,
address_update: domain::AddressUpdate,
_payment_id: String,
key_store: &domain::MerchantKeyStore,
_storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
let address = Conversion::convert(this)
.await
@@ -190,10 +190,10 @@ mod storage {
async fn insert_address_for_payments(
&self,
_payment_id: &str,
address: domain::Address,
address: domain::PaymentAddress,
key_store: &domain::MerchantKeyStore,
_storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
address
.construct_new()
@@ -214,7 +214,7 @@ mod storage {
#[instrument(skip_all)]
async fn insert_address_for_customers(
&self,
address: domain::Address,
address: domain::CustomerAddress,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Address, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
@@ -320,7 +320,7 @@ mod storage {
address_id: &str,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
let database_call = || async {
storage_types::Address::find_by_merchant_id_payment_id_address_id(
@@ -384,12 +384,12 @@ mod storage {
#[instrument(skip_all)]
async fn update_address_for_payments(
&self,
this: domain::Address,
this: domain::PaymentAddress,
address_update: domain::AddressUpdate,
payment_id: String,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
let address = Conversion::convert(this)
.await
@@ -456,10 +456,10 @@ mod storage {
async fn insert_address_for_payments(
&self,
payment_id: &str,
address: domain::Address,
address: domain::PaymentAddress,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
let address_new = address
.clone()
.construct_new()
@@ -547,7 +547,7 @@ mod storage {
#[instrument(skip_all)]
async fn insert_address_for_customers(
&self,
address: domain::Address,
address: domain::CustomerAddress,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Address, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
@@ -635,7 +635,7 @@ impl AddressInterface for MockDb {
address_id: &str,
key_store: &domain::MerchantKeyStore,
_storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
match self
.addresses
.lock()
@@ -688,18 +688,18 @@ impl AddressInterface for MockDb {
async fn update_address_for_payments(
&self,
this: domain::Address,
this: domain::PaymentAddress,
address_update: domain::AddressUpdate,
_payment_id: String,
key_store: &domain::MerchantKeyStore,
_storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
let updated_addr = self
.addresses
.lock()
.await
.iter_mut()
.find(|address| address.address_id == this.address_id)
.find(|address| address.address_id == this.address.address_id)
.map(|a| {
let address_updated =
AddressUpdateInternal::from(address_update).create_address(a.clone());
@@ -721,10 +721,10 @@ impl AddressInterface for MockDb {
async fn insert_address_for_payments(
&self,
_payment_id: &str,
address_new: domain::Address,
address_new: domain::PaymentAddress,
key_store: &domain::MerchantKeyStore,
_storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
let mut addresses = self.addresses.lock().await;
let address = Conversion::convert(address_new)
@@ -741,7 +741,7 @@ impl AddressInterface for MockDb {
async fn insert_address_for_customers(
&self,
address_new: domain::Address,
address_new: domain::CustomerAddress,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Address, errors::StorageError> {
let mut addresses = self.addresses.lock().await;

View File

@@ -121,12 +121,12 @@ impl AddressInterface for KafkaStore {
async fn update_address_for_payments(
&self,
this: domain::Address,
this: domain::PaymentAddress,
address: domain::AddressUpdate,
payment_id: String,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
self.diesel_store
.update_address_for_payments(this, address, payment_id, key_store, storage_scheme)
.await
@@ -135,10 +135,10 @@ impl AddressInterface for KafkaStore {
async fn insert_address_for_payments(
&self,
payment_id: &str,
address: domain::Address,
address: domain::PaymentAddress,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
self.diesel_store
.insert_address_for_payments(payment_id, address, key_store, storage_scheme)
.await
@@ -151,7 +151,7 @@ impl AddressInterface for KafkaStore {
address_id: &str,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<domain::Address, errors::StorageError> {
) -> CustomResult<domain::PaymentAddress, errors::StorageError> {
self.diesel_store
.find_address_by_merchant_id_payment_id_address_id(
merchant_id,
@@ -165,7 +165,7 @@ impl AddressInterface for KafkaStore {
async fn insert_address_for_customers(
&self,
address: domain::Address,
address: domain::CustomerAddress,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Address, errors::StorageError> {
self.diesel_store

View File

@@ -662,6 +662,7 @@ impl PaymentMethodInterface for MockDb {
status: payment_method_new.status,
client_secret: payment_method_new.client_secret,
network_transaction_id: payment_method_new.network_transaction_id,
payment_method_billing_address: payment_method_new.payment_method_billing_address,
};
payment_methods.push(payment_method.clone());
Ok(payment_method)

View File

@@ -35,13 +35,119 @@ pub struct Address {
#[serde(skip_serializing)]
#[serde(with = "custom_serde::iso8601")]
pub modified_at: PrimitiveDateTime,
pub customer_id: Option<String>,
pub merchant_id: String,
pub payment_id: Option<String>,
pub updated_by: String,
pub email: crypto::OptionalEncryptableEmail,
}
/// Based on the flow, appropriate address has to be used
/// In case of Payments, The `PaymentAddress`[PaymentAddress] has to be used
/// which contains only the `Address`[Address] object and `payment_id` and optional `customer_id`
#[derive(Debug, Clone)]
pub struct PaymentAddress {
pub address: Address,
pub payment_id: String,
// This is present in `PaymentAddress` because even `payouts` uses `PaymentAddress`
pub customer_id: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CustomerAddress {
pub address: Address,
pub customer_id: String,
}
#[async_trait]
impl behaviour::Conversion for CustomerAddress {
type DstType = diesel_models::address::Address;
type NewDstType = diesel_models::address::AddressNew;
async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
let converted_address = Address::convert(self.address).await?;
Ok(diesel_models::address::Address {
customer_id: Some(self.customer_id),
payment_id: None,
..converted_address
})
}
async fn convert_back(
other: Self::DstType,
key: &Secret<Vec<u8>>,
) -> CustomResult<Self, ValidationError> {
let customer_id =
other
.customer_id
.clone()
.ok_or(ValidationError::MissingRequiredField {
field_name: "cutomer_id".to_string(),
})?;
let address = Address::convert_back(other, key).await?;
Ok(Self {
address,
customer_id,
})
}
async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
let address_new = Address::construct_new(self.address).await?;
Ok(Self::NewDstType {
customer_id: Some(self.customer_id),
payment_id: None,
..address_new
})
}
}
#[async_trait]
impl behaviour::Conversion for PaymentAddress {
type DstType = diesel_models::address::Address;
type NewDstType = diesel_models::address::AddressNew;
async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
let converted_address = Address::convert(self.address).await?;
Ok(diesel_models::address::Address {
customer_id: self.customer_id,
payment_id: Some(self.payment_id),
..converted_address
})
}
async fn convert_back(
other: Self::DstType,
key: &Secret<Vec<u8>>,
) -> CustomResult<Self, ValidationError> {
let payment_id = other
.payment_id
.clone()
.ok_or(ValidationError::MissingRequiredField {
field_name: "payment_id".to_string(),
})?;
let customer_id = other.customer_id.clone();
let address = Address::convert_back(other, key).await?;
Ok(Self {
address,
payment_id,
customer_id,
})
}
async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
let address_new = Address::construct_new(self.address).await?;
Ok(Self::NewDstType {
customer_id: self.customer_id,
payment_id: Some(self.payment_id),
..address_new
})
}
}
#[async_trait]
impl behaviour::Conversion for Address {
type DstType = diesel_models::address::Address;
@@ -64,11 +170,11 @@ impl behaviour::Conversion for Address {
country_code: self.country_code,
created_at: self.created_at,
modified_at: self.modified_at,
customer_id: self.customer_id,
merchant_id: self.merchant_id,
payment_id: self.payment_id,
updated_by: self.updated_by,
email: self.email.map(Encryption::from),
payment_id: None,
customer_id: None,
})
}
@@ -95,10 +201,8 @@ impl behaviour::Conversion for Address {
country_code: other.country_code,
created_at: other.created_at,
modified_at: other.modified_at,
customer_id: other.customer_id,
merchant_id: other.merchant_id,
payment_id: other.payment_id,
updated_by: other.updated_by,
merchant_id: other.merchant_id,
email: other.email.async_lift(inner_decrypt_email).await?,
})
}
@@ -123,13 +227,13 @@ impl behaviour::Conversion for Address {
last_name: self.last_name.map(Encryption::from),
phone_number: self.phone_number.map(Encryption::from),
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,
updated_by: self.updated_by,
email: self.email.map(Encryption::from),
customer_id: None,
payment_id: None,
})
}
}

View File

@@ -577,7 +577,7 @@ pub trait CustomerAddress {
customer_id: &str,
key: &[u8],
storage_scheme: storage::enums::MerchantStorageScheme,
) -> CustomResult<domain::Address, common_utils::errors::CryptoError>;
) -> CustomResult<domain::CustomerAddress, common_utils::errors::CryptoError>;
}
#[async_trait::async_trait]
@@ -645,9 +645,9 @@ impl CustomerAddress for api_models::customers::CustomerRequest {
customer_id: &str,
key: &[u8],
storage_scheme: storage::enums::MerchantStorageScheme,
) -> CustomResult<domain::Address, common_utils::errors::CryptoError> {
) -> CustomResult<domain::CustomerAddress, common_utils::errors::CryptoError> {
async {
Ok(domain::Address {
let address = domain::Address {
id: None,
city: address_details.city,
country: address_details.country,
@@ -685,10 +685,8 @@ impl CustomerAddress for api_models::customers::CustomerRequest {
.async_lift(|inner| encrypt_optional(inner, key))
.await?,
country_code: self.phone_country_code.clone(),
customer_id: Some(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(),
updated_by: storage_scheme.to_string(),
@@ -698,6 +696,11 @@ impl CustomerAddress for api_models::customers::CustomerRequest {
.cloned()
.async_lift(|inner| encrypt_optional(inner.map(|inner| inner.expose()), key))
.await?,
};
Ok(domain::CustomerAddress {
address,
customer_id: customer_id.to_string(),
})
}
.await

View File

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

View File

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

View File

@@ -8162,6 +8162,14 @@
"type": "boolean",
"description": "Indicates if the payment method has been set to default or not",
"example": true
},
"billing": {
"allOf": [
{
"$ref": "#/components/schemas/Address"
}
],
"nullable": true
}
}
},