Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
42 changes: 5 additions & 37 deletions crates/diesel_models/src/payment_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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(),
}
}
}
1 change: 1 addition & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,7 @@ diesel::table! {
network_transaction_id -> Nullable<Varchar>,
#[max_length = 128]
client_secret -> Nullable<Varchar>,
payment_method_billing_address -> Nullable<Bytea>,
}
}

Expand Down
81 changes: 66 additions & 15 deletions crates/router/src/core/payment_methods/cards.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
str::FromStr,
};

Expand Down Expand Up @@ -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(
Expand All @@ -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 {
Expand All @@ -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,
)
Expand Down Expand Up @@ -231,6 +248,7 @@ pub async fn get_or_insert_payment_method(
None,
None,
merchant_account.storage_scheme,
None,
)
.await
} else {
Expand Down Expand Up @@ -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?;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -688,6 +706,7 @@ pub async fn add_payment_method(
None,
None,
merchant_account.storage_scheme,
None,
)
.await?;

Expand All @@ -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,
Expand All @@ -733,6 +753,7 @@ pub async fn insert_payment_method(
None,
network_transaction_id,
storage_scheme,
payment_method_billing_address,
)
.await
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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());

Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down
Loading
Loading