From c2f7e6160e8640f832fbc26845d25280baa9e5c4 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Mon, 14 Oct 2024 03:23:26 +0530 Subject: [PATCH 01/10] add support for network token migration --- crates/api_models/src/events/payment.rs | 12 +- crates/api_models/src/payment_methods.rs | 102 +++- crates/cards/src/validate.rs | 1 + crates/diesel_models/src/payment_method.rs | 26 + .../router/src/core/payment_methods/cards.rs | 538 +++++++++++++++++- .../router/src/core/payments/tokenization.rs | 4 +- .../router/src/types/api/payment_methods.rs | 6 +- 7 files changed, 665 insertions(+), 24 deletions(-) diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index aa8cd43ca97f..258e32064ae1 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -13,7 +13,7 @@ use crate::{ ListCountriesCurrenciesResponse, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCollectLinkResponse, PaymentMethodDeleteResponse, PaymentMethodListRequest, PaymentMethodListResponse, - PaymentMethodResponse, PaymentMethodUpdate, + PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodUpdate, }, payments::{ ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, @@ -126,6 +126,16 @@ impl ApiEventMetric for PaymentMethodResponse { } } +impl ApiEventMetric for PaymentMethodMigrateResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::PaymentMethod { + payment_method_id: self.payment_method_response.payment_method_id.clone(), + payment_method: self.payment_method_response.payment_method, + payment_method_type: self.payment_method_response.payment_method_type, + }) + } +} + impl ApiEventMetric for PaymentMethodUpdate {} impl ApiEventMetric for DefaultPaymentMethod { diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 1a82dc0e26bd..231b3a391033 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -243,6 +243,14 @@ pub struct PaymentMethodMigrate { /// Card Details pub card: Option, + //to skip card expiry validation + //if it is set to true, card expiry validation will be skipped + #[serde(default)] + pub skip_card_expiry_validation: Option, + + /// Network token details + pub network_token: Option, + /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object. pub metadata: Option, @@ -274,6 +282,15 @@ pub struct PaymentMethodMigrate { pub network_transaction_id: Option, } +#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PaymentMethodMigrateResponse { + pub payment_method_response: PaymentMethodResponse, + + pub card_migrated: Option, + + pub network_token_migrated: Option, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentsMandateReference( pub HashMap, @@ -537,6 +554,53 @@ pub struct MigrateCardDetail { pub card_type: Option, } +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct MigrateNetworkTokenData { + /// Token Number + #[schema(value_type = String,example = "4111111145551142")] + pub network_token_number: masking::Secret, + + /// Token Expiry Month + #[schema(value_type = String,example = "10")] + pub network_token_exp_month: masking::Secret, + + /// Card Expiry Year + #[schema(value_type = String,example = "25")] + pub network_token_exp_year: masking::Secret, + + /// Card Holder Name + #[schema(value_type = String,example = "John Doe")] + pub card_holder_name: Option>, + + /// Card Holder's Nick Name + #[schema(value_type = Option,example = "John Doe")] + pub nick_name: Option>, + + /// Card Issuing Country + pub card_issuing_country: Option, + + /// Card's Network + #[schema(value_type = Option)] + pub card_network: Option, + + /// Issuer Bank for Card + pub card_issuer: Option, + + /// Card Type + pub card_type: Option, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct MigrateNetworkTokenDetail { + /// Network token details + pub network_token_data: MigrateNetworkTokenData, + + /// Network token requestor reference id + pub network_token_requestor_ref_id: String, +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -2088,6 +2152,11 @@ pub struct PaymentMethodRecord { pub original_transaction_amount: Option, pub original_transaction_currency: Option, pub line_number: Option, + pub network_token_number: Option>, + pub network_token_expiry_month: Option>, + pub network_token_expiry_year: Option>, + pub network_token_requestor_ref_id: Option, + pub skip_card_expiry_validation: Option, } #[derive(Debug, Default, serde::Serialize)] @@ -2113,8 +2182,10 @@ pub enum MigrationStatus { Failed, } -type PaymentMethodMigrationResponseType = - (Result, PaymentMethodRecord); +type PaymentMethodMigrationResponseType = ( + Result, + PaymentMethodRecord, +); #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -2123,10 +2194,10 @@ impl From for PaymentMethodMigrationResponse fn from((response, record): PaymentMethodMigrationResponseType) -> Self { match response { Ok(res) => Self { - payment_method_id: Some(res.payment_method_id), - payment_method: res.payment_method, - payment_method_type: res.payment_method_type, - customer_id: res.customer_id, + payment_method_id: Some(res.payment_method_response.payment_method_id), + payment_method: res.payment_method_response.payment_method, + payment_method_type: res.payment_method_response.payment_method_type, + customer_id: res.payment_method_response.customer_id, migration_status: MigrationStatus::Success, migration_error: None, card_number_masked: Some(record.card_number_masked), @@ -2190,12 +2261,28 @@ impl From for PaymentMethodMigrate { card_exp_month: record.card_expiry_month, card_exp_year: record.card_expiry_year, card_holder_name: record.name, - card_network: None, + card_network: None, //? card_type: None, card_issuer: None, card_issuing_country: None, nick_name: Some(record.nick_name), }), + network_token: Some(MigrateNetworkTokenDetail { + network_token_data: MigrateNetworkTokenData { + network_token_number: record.network_token_number.unwrap_or_default(), + network_token_exp_month: record.network_token_expiry_month.unwrap_or_default(), + network_token_exp_year: record.network_token_expiry_year.unwrap_or_default(), + card_holder_name: None, //? + nick_name: None, + card_issuing_country: None, + card_network: None, //? + card_issuer: None, + card_type: None, + }, + network_token_requestor_ref_id: record + .network_token_requestor_ref_id + .unwrap_or_default(), + }), payment_method: record.payment_method, payment_method_type: record.payment_method_type, payment_method_issuer: None, @@ -2227,6 +2314,7 @@ impl From for PaymentMethodMigrate { wallet: None, payment_method_data: None, network_transaction_id: record.original_transaction_id.into(), + skip_card_expiry_validation: record.skip_card_expiry_validation, } } } diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index de4502698901..c638503b2558 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -27,6 +27,7 @@ pub struct CardNumberValidationErr(&'static str); /// Card number #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct CardNumber(StrongSecret); +pub struct NetworkToken(CardNumber); //will take this up in diff pr impl CardNumber { pub fn get_card_isin(&self) -> String { diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs index 3ad58e62dcf9..0c4b82a519b3 100644 --- a/crates/diesel_models/src/payment_method.rs +++ b/crates/diesel_models/src/payment_method.rs @@ -252,6 +252,11 @@ pub enum PaymentMethodUpdate { ConnectorMandateDetailsUpdate { connector_mandate_details: Option, }, + NetworkTokenDataUpdate { + network_token_requestor_reference_id: Option, + network_token_locker_id: Option, + network_token_payment_method_data: Option, + }, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -589,6 +594,27 @@ impl From for PaymentMethodUpdateInternal { network_token_locker_id: None, network_token_payment_method_data: None, }, + PaymentMethodUpdate::NetworkTokenDataUpdate { + network_token_requestor_reference_id, + network_token_locker_id, + network_token_payment_method_data, + } => Self { + metadata: None, + payment_method_data: None, + last_used_at: None, + status: None, + locker_id: None, + payment_method: None, + connector_mandate_details: None, + updated_by: None, + payment_method_issuer: None, + payment_method_type: None, + last_modified: common_utils::date_time::now(), + network_transaction_id: None, + network_token_requestor_reference_id, + network_token_locker_id, + network_token_payment_method_data, + }, } } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 3c09db61a392..54dc71ba31e9 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -405,9 +405,9 @@ pub async fn migrate_payment_method( merchant_id: &id_type::MerchantId, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, -) -> errors::RouterResponse { +) -> errors::RouterResponse { let mut req = req; - let card_details = req.card.as_ref().get_required_value("card")?; + let card_details = &req.card.clone().get_required_value("card")?; let card_number_validation_result = cards::CardNumber::from_str(card_details.card_number.peek()); @@ -434,25 +434,56 @@ pub async fn migrate_payment_method( .await?; }; - match card_number_validation_result { + let skip_card_expiry_validation = req.skip_card_expiry_validation.unwrap_or(false); //here if it is not present in the request it will be set to false + + let resp = match card_number_validation_result { Ok(card_number) => { let payment_method_create_request = api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate( card_number, &req, ); - get_client_secret_or_add_payment_method( - &state, - payment_method_create_request, - merchant_account, - key_store, - ) - .await + + let validation_result = helpers::validate_card_expiry( + &card_details.card_exp_month, + &card_details.card_exp_year, + ); + + if skip_card_expiry_validation { + //will refactor using match statement + if validation_result.is_err() { + skip_locker_call_and_migrate_payment_method( + state.clone(), + &req, + merchant_id.to_owned(), + key_store, + merchant_account, + card_bin_details.clone(), + ) + .await + } else { + get_client_secret_or_add_payment_method_for_migrate_api( + &state, + payment_method_create_request, + merchant_account, + key_store, + ) + .await + } + } else { + get_client_secret_or_add_payment_method_for_migrate_api( + &state, + payment_method_create_request, + merchant_account, + key_store, + ) + .await + } } Err(card_validation_error) => { logger::debug!("Card number to be migrated is invalid, skip saving in locker {card_validation_error}"); skip_locker_call_and_migrate_payment_method( - state, + state.clone(), &req, merchant_id.to_owned(), key_store, @@ -461,6 +492,144 @@ pub async fn migrate_payment_method( ) .await } + }?; + let payment_method_response = match resp { + services::ApplicationResponse::Json(response) => response, + _ => Err(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch the payment method response")?, // Handle the case where the response is not what you expect + }; + + let pm_id = payment_method_response.payment_method_id.clone(); + + let network_token = req.network_token.clone(); + match network_token { + Some(network_token) => { + let network_token_data = network_token.network_token_data.clone(); + let network_token_requestor_ref_id = + network_token.network_token_requestor_ref_id.clone(); + let network_token_validation = + cards::CardNumber::from_str(network_token_data.network_token_number.peek()); + + match network_token_validation { + Ok(network_token) => { + let payment_method_create_request = + api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate( + network_token.clone(), + &req, + ); + let customer_id = req.customer_id.clone().get_required_value("customer_id")?; + + let network_token_details = api::CardDetail { + card_number: network_token, + card_exp_month: network_token_data.network_token_exp_month.clone(), + card_exp_year: network_token_data.network_token_exp_year.clone(), + card_holder_name: network_token_data.card_holder_name.clone(), + nick_name: network_token_data.nick_name.clone(), + card_issuing_country: network_token_data.card_issuing_country.clone(), + card_network: network_token_data.card_network.clone(), + card_issuer: network_token_data.card_issuer.clone(), + card_type: network_token_data.card_type.clone(), + }; + + let token_resp = Box::pin(add_card_to_locker( + &state, + payment_method_create_request.clone(), + &network_token_details, + &customer_id, + merchant_account, + None, + )) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add Card Failed"); + + match token_resp { + Ok(resp) => { + let (token_pm_resp, _duplication_check) = resp; + let pm_token_details = token_pm_resp.card.as_ref().map(|card| { + PaymentMethodsData::Card(CardDetailsPaymentMethod::from( + card.clone(), + )) + }); + let pm_network_token_data_encrypted = pm_token_details + .async_map(|pm_card| { + create_encrypted_data(&state, key_store, pm_card) + }) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt payment method data")?; + + let pm_update = storage::PaymentMethodUpdate::NetworkTokenDataUpdate { + network_token_requestor_reference_id: Some( + network_token_requestor_ref_id, + ), + network_token_locker_id: Some(token_pm_resp.payment_method_id), + network_token_payment_method_data: pm_network_token_data_encrypted + .map(Into::into), + }; + let db = &*state.store; + let existing_pm = db + .find_payment_method( + &((&state).into()), + key_store, + &pm_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; //should propogate err? + + db.update_payment_method( + &((&state).into()), + key_store, + existing_pm, + pm_update, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; + + return Ok(services::ApplicationResponse::Json( + api::PaymentMethodMigrateResponse { + payment_method_response, + card_migrated: Some(true), + network_token_migrated: Some(true), + }, + )); + } + Err(_) => { + return Ok(services::ApplicationResponse::Json( + api::PaymentMethodMigrateResponse { + payment_method_response, + card_migrated: Some(true), + network_token_migrated: Some(false), + }, + )) + } + } + } + Err(_) => { + return Ok(services::ApplicationResponse::Json( + api::PaymentMethodMigrateResponse { + payment_method_response, + card_migrated: Some(true), + network_token_migrated: Some(false), + }, + )) + } + } + } + None => { + return Ok(services::ApplicationResponse::Json( + api::PaymentMethodMigrateResponse { + payment_method_response, + card_migrated: Some(true), + network_token_migrated: None, + }, + )) + } } } @@ -918,6 +1087,96 @@ pub async fn get_client_secret_or_add_payment_method( } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[instrument(skip_all)] +pub async fn get_client_secret_or_add_payment_method_for_migrate_api( + state: &routes::SessionState, + req: api::PaymentMethodCreate, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, +) -> errors::RouterResponse { + let merchant_id = merchant_account.get_id(); + let customer_id = req.customer_id.clone().get_required_value("customer_id")?; + + #[cfg(not(feature = "payouts"))] + let condition = req.card.is_some(); + #[cfg(feature = "payouts")] + let condition = req.card.is_some() || req.bank_transfer.is_some() || req.wallet.is_some(); + + let payment_method_billing_address: Option>> = req + .billing + .clone() + .async_map(|billing| create_encrypted_data(state, key_store, billing)) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt Payment method billing address")?; + + let connector_mandate_details = req + .connector_mandate_details + .clone() + .map(serde_json::to_value) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + if condition { + Box::pin(add_payment_method_for_migrate_api( + state, + req, + merchant_account, + key_store, + )) + .await + } else { + let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); + + let res = create_payment_method( + state, + &req, + &customer_id, + payment_method_id.as_str(), + None, + merchant_id, + None, + None, + None, + key_store, + connector_mandate_details, + Some(enums::PaymentMethodStatus::AwaitingData), + None, + merchant_account.storage_scheme, + payment_method_billing_address.map(Into::into), + None, + None, + None, + None, + ) + .await?; + + if res.status == enums::PaymentMethodStatus::AwaitingData { + add_payment_method_status_update_task( + &*state.store, + &res, + enums::PaymentMethodStatus::AwaitingData, + enums::PaymentMethodStatus::Inactive, + merchant_id, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Failed to add payment method status update task in process tracker", + )?; + } + + Ok(services::api::ApplicationResponse::Json( + api::PaymentMethodResponse::foreign_from((None, res)), + )) + } +} + #[instrument(skip_all)] pub fn authenticate_pm_client_secret_and_check_expiry( req_client_secret: &String, @@ -1411,6 +1670,263 @@ pub async fn add_payment_method( Ok(services::ApplicationResponse::Json(resp)) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[instrument(skip_all)] +pub async fn add_payment_method_for_migrate_api( + state: &routes::SessionState, + req: api::PaymentMethodCreate, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, +) -> errors::RouterResponse { + req.validate()?; + let db = &*state.store; + let merchant_id = merchant_account.get_id(); + let customer_id = req.customer_id.clone().get_required_value("customer_id")?; + let payment_method = req.payment_method.get_required_value("payment_method")?; + let payment_method_billing_address: Option>> = req + .billing + .clone() + .async_map(|billing| create_encrypted_data(state, key_store, billing)) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt Payment method billing address")?; + + let connector_mandate_details = req + .connector_mandate_details + .clone() + .map(serde_json::to_value) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let response = match payment_method { + #[cfg(feature = "payouts")] + api_enums::PaymentMethod::BankTransfer => match req.bank_transfer.clone() { + Some(bank) => add_bank_to_locker( + state, + req.clone(), + merchant_account, + key_store, + &bank, + &customer_id, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add PaymentMethod Failed"), + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), + }, + api_enums::PaymentMethod::Card => match req.card.clone() { + Some(card) => { + let mut card_details = card; + card_details = helpers::populate_bin_details_for_payment_method_create( + card_details.clone(), + db, + ) + .await; + helpers::validate_card_expiry( + &card_details.card_exp_month, + &card_details.card_exp_year, + )?; + Box::pin(add_card_to_locker( + state, + req.clone(), + &card_details, + &customer_id, + merchant_account, + None, + )) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add Card Failed") + } + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), + }, + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), + }; + + let (mut resp, duplication_check) = response?; + + match duplication_check { + Some(duplication_check) => match duplication_check { + payment_methods::DataDuplicationCheck::Duplicated => { + let existing_pm = get_or_insert_payment_method( + state, + req.clone(), + &mut resp, + merchant_account, + &customer_id, + key_store, + ) + .await?; + + resp.client_secret = existing_pm.client_secret; + } + payment_methods::DataDuplicationCheck::MetaDataChanged => { + if let Some(card) = req.card.clone() { + let existing_pm = get_or_insert_payment_method( + state, + req.clone(), + &mut resp, + merchant_account, + &customer_id, + key_store, + ) + .await?; + + let client_secret = existing_pm.client_secret.clone(); + + delete_card_from_locker( + state, + &customer_id, + merchant_id, + existing_pm + .locker_id + .as_ref() + .unwrap_or(&existing_pm.payment_method_id), + ) + .await?; + + let add_card_resp = add_card_hs( + state, + req.clone(), + &card, + &customer_id, + merchant_account, + api::enums::LockerChoice::HyperswitchCardVault, + Some( + existing_pm + .locker_id + .as_ref() + .unwrap_or(&existing_pm.payment_method_id), + ), + ) + .await; + + if let Err(err) = add_card_resp { + logger::error!(vault_err=?err); + db.delete_payment_method_by_merchant_id_payment_method_id( + &(state.into()), + key_store, + merchant_id, + &resp.payment_method_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + + Err(report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while updating card metadata changes"))? + }; + + let existing_pm_data = + get_card_details_without_locker_fallback(&existing_pm, state).await?; + + let updated_card = Some(api::CardDetailFromLocker { + scheme: existing_pm.scheme.clone(), + last4_digits: Some(card.card_number.get_last4()), + issuer_country: card + .card_issuing_country + .or(existing_pm_data.issuer_country), + card_isin: Some(card.card_number.get_card_isin()), + card_number: Some(card.card_number), + expiry_month: Some(card.card_exp_month), + expiry_year: Some(card.card_exp_year), + card_token: None, + card_fingerprint: None, + card_holder_name: card + .card_holder_name + .or(existing_pm_data.card_holder_name), + nick_name: card.nick_name.or(existing_pm_data.nick_name), + card_network: card.card_network.or(existing_pm_data.card_network), + card_issuer: card.card_issuer.or(existing_pm_data.card_issuer), + card_type: card.card_type.or(existing_pm_data.card_type), + saved_to_locker: true, + }); + + let updated_pmd = updated_card.as_ref().map(|card| { + PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())) + }); + let pm_data_encrypted: Option>> = + updated_pmd + .async_map(|updated_pmd| { + create_encrypted_data(state, key_store, updated_pmd) + }) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt payment method data")?; + + let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate { + payment_method_data: pm_data_encrypted.map(Into::into), + }; + + db.update_payment_method( + &(state.into()), + key_store, + existing_pm, + pm_update, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; + + resp.client_secret = client_secret; + } + } + }, + None => { + let pm_metadata = resp.metadata.as_ref().map(|data| data.peek()); + + let locker_id = if resp.payment_method == Some(api_enums::PaymentMethod::Card) + || resp.payment_method == Some(api_enums::PaymentMethod::BankTransfer) + { + Some(resp.payment_method_id) + } else { + None + }; + resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm"); + let pm = insert_payment_method( + state, + &resp, + &req, + key_store, + merchant_id, + &customer_id, + pm_metadata.cloned(), + None, + locker_id, + connector_mandate_details, + req.network_transaction_id.clone(), + merchant_account.storage_scheme, + payment_method_billing_address.map(Into::into), + None, + None, + None, + ) + .await?; + + resp.client_secret = pm.client_secret; + } + } + + Ok(services::ApplicationResponse::Json(resp)) +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all)] pub async fn get_fingerprint_id_from_locker< diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 6815d2dfc4fe..511f8b18ebc2 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -1021,7 +1021,7 @@ pub async fn save_network_token_in_locker( { Ok((token_response, network_token_requestor_ref_id)) => { // Only proceed if the tokenization was successful - let card_data = api::CardDetail { + let network_token_data = api::CardDetail { card_number: token_response.token.clone(), card_exp_month: token_response.token_expiry_month.clone(), card_exp_year: token_response.token_expiry_year.clone(), @@ -1036,7 +1036,7 @@ pub async fn save_network_token_in_locker( let (res, dc) = Box::pin(payment_methods::cards::add_card_to_locker( state, payment_method_request, - &card_data, + &network_token_data, &customer_id, merchant_account, None, diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index d66bf079d42a..8c631acc3f35 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -22,9 +22,9 @@ pub use api_models::payment_methods::{ PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList, PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate, - PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, - TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, - TokenizedWalletValue2, + PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, + TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, + TokenizedWalletValue1, TokenizedWalletValue2, }; use error_stack::report; From 0892ec036a9a132faf66858b393e1a0244cafec3 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Thu, 17 Oct 2024 03:05:11 +0530 Subject: [PATCH 02/10] code refactoring --- crates/api_models/src/payment_methods.rs | 26 +- .../router/src/core/payment_methods/cards.rs | 371 +++++++++++------- 2 files changed, 246 insertions(+), 151 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 231b3a391033..96e1d4dfc3b4 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -4,10 +4,7 @@ use std::str::FromStr; use cards::CardNumber; use common_utils::{ - consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, - crypto::OptionalEncryptableName, - id_type, link_utils, pii, - types::{MinorUnit, Percentage, Surcharge}, + consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, id_type, link_utils, pii, types::{MinorUnit, Percentage, Surcharge} }; use masking::PeekInterface; use serde::de; @@ -2137,7 +2134,7 @@ pub struct PaymentMethodRecord { pub card_expiry_month: masking::Secret, pub card_expiry_year: masking::Secret, pub card_scheme: Option, - pub original_transaction_id: String, + pub original_transaction_id: Option, pub billing_address_zip: masking::Secret, pub billing_address_state: masking::Secret, pub billing_address_first_name: masking::Secret, @@ -2173,6 +2170,9 @@ pub struct PaymentMethodMigrationResponse { #[serde(skip_serializing_if = "Option::is_none")] pub migration_error: Option, pub card_number_masked: Option>, + pub card_migrated: Option, + pub network_token_migrated: Option, + } #[derive(Debug, Default, serde::Serialize)] @@ -2202,6 +2202,8 @@ impl From for PaymentMethodMigrationResponse migration_error: None, card_number_masked: Some(record.card_number_masked), line_number: record.line_number, + card_migrated: res.card_migrated, + network_token_migrated: res.network_token_migrated, }, Err(e) => Self { customer_id: Some(record.customer_id), @@ -2260,22 +2262,22 @@ impl From for PaymentMethodMigrate { card_number: record.raw_card_number.unwrap_or(record.card_number_masked), card_exp_month: record.card_expiry_month, card_exp_year: record.card_expiry_year, - card_holder_name: record.name, - card_network: None, //? + card_holder_name: record.name.clone(), + card_network: None, card_type: None, card_issuer: None, card_issuing_country: None, - nick_name: Some(record.nick_name), + nick_name: Some(record.nick_name.clone()), }), network_token: Some(MigrateNetworkTokenDetail { network_token_data: MigrateNetworkTokenData { network_token_number: record.network_token_number.unwrap_or_default(), network_token_exp_month: record.network_token_expiry_month.unwrap_or_default(), network_token_exp_year: record.network_token_expiry_year.unwrap_or_default(), - card_holder_name: None, //? - nick_name: None, + card_holder_name: record.name, + nick_name: Some(record.nick_name), card_issuing_country: None, - card_network: None, //? + card_network: None, card_issuer: None, card_type: None, }, @@ -2313,7 +2315,7 @@ impl From for PaymentMethodMigrate { #[cfg(feature = "payouts")] wallet: None, payment_method_data: None, - network_transaction_id: record.original_transaction_id.into(), + network_transaction_id: record.original_transaction_id, skip_card_expiry_validation: record.skip_card_expiry_validation, } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 54dc71ba31e9..7477cfbb6dca 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -412,8 +412,14 @@ pub async fn migrate_payment_method( let card_number_validation_result = cards::CardNumber::from_str(card_details.card_number.peek()); - let card_bin_details = - populate_bin_details_for_masked_card(card_details, &*state.store).await?; + let skip_card_expiry_validation = req.skip_card_expiry_validation.unwrap_or(false); //here if `skip_card_expiry_validation` is not present in the request it will be set to false + + let card_bin_details = populate_bin_details_for_masked_card( + card_details, + &*state.store, + skip_card_expiry_validation, + ) + .await?; req.card = Some(api_models::payment_methods::MigrateCardDetail { card_issuing_country: card_bin_details.issuer_country.clone(), @@ -434,7 +440,7 @@ pub async fn migrate_payment_method( .await?; }; - let skip_card_expiry_validation = req.skip_card_expiry_validation.unwrap_or(false); //here if it is not present in the request it will be set to false + let should_require_connector_mandate_details = req.network_token.is_none(); let resp = match card_number_validation_result { Ok(card_number) => { @@ -450,8 +456,10 @@ pub async fn migrate_payment_method( ); if skip_card_expiry_validation { - //will refactor using match statement + //skip_card_expiry_validation if true, then error will not be propogated and card will not be saved in locker, + // but will update the payment_method_data with bin details and migrate connector mandate details if present. if validation_result.is_err() { + logger::debug!("Skip storing card in locker as skip_card_expiry_validation is {} and card is expired",skip_card_expiry_validation); skip_locker_call_and_migrate_payment_method( state.clone(), &req, @@ -459,9 +467,11 @@ pub async fn migrate_payment_method( key_store, merchant_account, card_bin_details.clone(), + should_require_connector_mandate_details, //when there is no card and no network token, should_require_connector_mandate_details is set to true and connector mandate details are required ) .await } else { + logger::debug!("Storing the card in locker and migrating the payment method"); get_client_secret_or_add_payment_method_for_migrate_api( &state, payment_method_create_request, @@ -471,6 +481,7 @@ pub async fn migrate_payment_method( .await } } else { + logger::debug!("Storing the card in locker and migrating the payment method"); get_client_secret_or_add_payment_method_for_migrate_api( &state, payment_method_create_request, @@ -489,6 +500,7 @@ pub async fn migrate_payment_method( key_store, merchant_account, card_bin_details.clone(), + should_require_connector_mandate_details, ) .await } @@ -502,135 +514,56 @@ pub async fn migrate_payment_method( let pm_id = payment_method_response.payment_method_id.clone(); let network_token = req.network_token.clone(); - match network_token { - Some(network_token) => { - let network_token_data = network_token.network_token_data.clone(); - let network_token_requestor_ref_id = - network_token.network_token_requestor_ref_id.clone(); - let network_token_validation = - cards::CardNumber::from_str(network_token_data.network_token_number.peek()); - - match network_token_validation { - Ok(network_token) => { - let payment_method_create_request = - api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate( - network_token.clone(), - &req, - ); - let customer_id = req.customer_id.clone().get_required_value("customer_id")?; - - let network_token_details = api::CardDetail { - card_number: network_token, - card_exp_month: network_token_data.network_token_exp_month.clone(), - card_exp_year: network_token_data.network_token_exp_year.clone(), - card_holder_name: network_token_data.card_holder_name.clone(), - nick_name: network_token_data.nick_name.clone(), - card_issuing_country: network_token_data.card_issuing_country.clone(), - card_network: network_token_data.card_network.clone(), - card_issuer: network_token_data.card_issuer.clone(), - card_type: network_token_data.card_type.clone(), - }; - let token_resp = Box::pin(add_card_to_locker( - &state, - payment_method_create_request.clone(), - &network_token_details, - &customer_id, - merchant_account, - None, - )) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Add Card Failed"); - - match token_resp { - Ok(resp) => { - let (token_pm_resp, _duplication_check) = resp; - let pm_token_details = token_pm_resp.card.as_ref().map(|card| { - PaymentMethodsData::Card(CardDetailsPaymentMethod::from( - card.clone(), - )) - }); - let pm_network_token_data_encrypted = pm_token_details - .async_map(|pm_card| { - create_encrypted_data(&state, key_store, pm_card) - }) - .await - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt payment method data")?; - - let pm_update = storage::PaymentMethodUpdate::NetworkTokenDataUpdate { - network_token_requestor_reference_id: Some( - network_token_requestor_ref_id, - ), - network_token_locker_id: Some(token_pm_resp.payment_method_id), - network_token_payment_method_data: pm_network_token_data_encrypted - .map(Into::into), - }; - let db = &*state.store; - let existing_pm = db - .find_payment_method( - &((&state).into()), - key_store, - &pm_id, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; //should propogate err? + let network_token_migrated = network_token.clone().map(|network_token| { + cards::CardNumber::from_str(network_token.network_token_data.network_token_number.peek()) + .map_err(|_| false) + }); // network_token_migrated is true if network_token_number passes the validation and will be migrated to locker - db.update_payment_method( - &((&state).into()), - key_store, - existing_pm, - pm_update, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; - - return Ok(services::ApplicationResponse::Json( - api::PaymentMethodMigrateResponse { - payment_method_response, - card_migrated: Some(true), - network_token_migrated: Some(true), - }, - )); - } - Err(_) => { - return Ok(services::ApplicationResponse::Json( - api::PaymentMethodMigrateResponse { - payment_method_response, - card_migrated: Some(true), - network_token_migrated: Some(false), - }, - )) - } - } - } - Err(_) => { - return Ok(services::ApplicationResponse::Json( - api::PaymentMethodMigrateResponse { - payment_method_response, - card_migrated: Some(true), - network_token_migrated: Some(false), - }, - )) - } - } + let v2 = match network_token_migrated { + Some(Ok(network_token)) => { + logger::debug!("Network token to be migrated is valid, saving in locker"); + let network_token_details = req + .network_token + .clone() + .get_required_value("network_token")?; + let network_token_requestor_ref_id = + network_token_details.network_token_requestor_ref_id.clone(); + let network_token_data = network_token_details.network_token_data.clone(); + + Some( + save_network_token_and_update_payment_method( + state, + &req, + key_store, + merchant_account, + network_token_data, + network_token_requestor_ref_id, + network_token, + pm_id, + ) + .await + .ok() + .unwrap_or(false), + ) + } + Some(Err(_)) => { + logger::debug!("Network token validation failed, not saving in locker"); + Some(false) } None => { - return Ok(services::ApplicationResponse::Json( - api::PaymentMethodMigrateResponse { - payment_method_response, - card_migrated: Some(true), - network_token_migrated: None, - }, - )) + logger::debug!("Network token data is not available"); + None } - } + }; + + Ok(services::ApplicationResponse::Json( + api::PaymentMethodMigrateResponse { + payment_method_response, + card_migrated: Some(true), + network_token_migrated: v2, + }, + )) } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -647,8 +580,28 @@ pub async fn migrate_payment_method( pub async fn populate_bin_details_for_masked_card( card_details: &api_models::payment_methods::MigrateCardDetail, db: &dyn db::StorageInterface, + skip_card_expiry_validation: bool, ) -> errors::CustomResult { - helpers::validate_card_expiry(&card_details.card_exp_month, &card_details.card_exp_year)?; + let validate = + helpers::validate_card_expiry(&card_details.card_exp_month, &card_details.card_exp_year); + + match validate { + Ok(_) => (), + Err(_) => { + utils::when(!skip_card_expiry_validation, || { + Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: format!("Invalid card expiry",) + })) + })? + + // if !skip_card_expiry_validation { + // Err(errors::ApiErrorResponse::InvalidRequestData { + // message: "Invalid card expiry".to_string(), + // })? //use when + // } + } + }; + let card_number = card_details.card_number.clone(); let (card_isin, _last4_digits) = get_card_bin_and_last4_digits_for_masked_card( @@ -850,19 +803,46 @@ pub async fn skip_locker_call_and_migrate_payment_method( key_store: &domain::MerchantKeyStore, merchant_account: &domain::MerchantAccount, card: api_models::payment_methods::CardDetailFromLocker, + should_require_connector_mandate_details: bool, ) -> errors::RouterResponse { let db = &*state.store; let customer_id = req.customer_id.clone().get_required_value("customer_id")?; - // In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details. - let connector_mandate_details_req = req - .connector_mandate_details - .clone() - .get_required_value("connector mandate details")?; + // // In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details. + // let connector_mandate_details_req = req + // .connector_mandate_details + // .clone() + // .get_required_value("connector mandate details")?; - let connector_mandate_details = serde_json::to_value(&connector_mandate_details_req) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to parse connector mandate details")?; + // let connector_mandate_details = serde_json::to_value(&connector_mandate_details_req) + // .change_context(errors::ApiErrorResponse::InternalServerError) + // .attach_printable("Failed to parse connector mandate details")?; + + let connector_mandate_details = if should_require_connector_mandate_details { + // If `should_require_connector_mandate_details` is true, we must extract it + let connector_mandate_details_req = req + .connector_mandate_details + .clone() + .get_required_value("connector mandate details")?; + + Some( + serde_json::to_value(&connector_mandate_details_req) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse connector mandate details")?, + ) + } else { + // If not required, serialize only if present in the request, else handle it (e.g., return null or skip) + let connector_mandate_details = req + .connector_mandate_details + .clone() + .map(|connector_mandate_details_req| { + serde_json::to_value(&connector_mandate_details_req) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse connector mandate details") + }) + .transpose()?; + connector_mandate_details + }; let payment_method_billing_address: Option>> = req .billing @@ -897,6 +877,8 @@ pub async fn skip_locker_call_and_migrate_payment_method( let payment_method_metadata: Option = req.metadata.as_ref().map(|data| data.peek()).cloned(); + let network_transaction_id = req.network_transaction_id.clone(); + let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); let current_time = common_utils::date_time::now(); @@ -916,11 +898,11 @@ pub async fn skip_locker_call_and_migrate_payment_method( scheme: req.card_network.clone().or(card.scheme.clone()), metadata: payment_method_metadata.map(Secret::new), payment_method_data: payment_method_data_encrypted.map(Into::into), - connector_mandate_details: Some(connector_mandate_details), + connector_mandate_details, customer_acceptance: None, client_secret: None, status: enums::PaymentMethodStatus::Active, - network_transaction_id: None, + network_transaction_id, payment_method_issuer_code: None, accepted_currency: None, token: None, @@ -966,6 +948,116 @@ pub async fn skip_locker_call_and_migrate_payment_method( )) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2"), + not(feature = "customer_v2") +))] +pub async fn save_network_token_and_update_payment_method( + state: routes::SessionState, + req: &api::PaymentMethodMigrate, + key_store: &domain::MerchantKeyStore, + merchant_account: &domain::MerchantAccount, + network_token_data: api_models::payment_methods::MigrateNetworkTokenData, + network_token_requestor_ref_id: String, + network_token: cards::CardNumber, + pm_id: String, +) -> errors::RouterResult { + let payment_method_create_request = + api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate( + network_token.clone(), + req, + ); + let customer_id = req.customer_id.clone().get_required_value("customer_id")?; + + let network_token_details = api::CardDetail { + card_number: network_token, + card_exp_month: network_token_data.network_token_exp_month.clone(), + card_exp_year: network_token_data.network_token_exp_year.clone(), + card_holder_name: network_token_data.card_holder_name.clone(), + nick_name: network_token_data.nick_name.clone(), + card_issuing_country: network_token_data.card_issuing_country.clone(), + card_network: network_token_data.card_network.clone(), + card_issuer: network_token_data.card_issuer.clone(), + card_type: network_token_data.card_type.clone(), + }; + + logger::debug!( + "Adding network token to locker for customer_id: {:?}", + customer_id + ); + + let token_resp = Box::pin(add_card_to_locker( + &state, + payment_method_create_request.clone(), + &network_token_details, + &customer_id, + merchant_account, + None, + )) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add Card Failed"); + + match token_resp { + Ok(resp) => { + logger::debug!("Network token added to locker"); + let (token_pm_resp, _duplication_check) = resp; + let pm_token_details = token_pm_resp + .card + .as_ref() + .map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()))); + let pm_network_token_data_encrypted = pm_token_details + .async_map(|pm_card| create_encrypted_data(&state, key_store, pm_card)) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt payment method data")?; + + let pm_update = storage::PaymentMethodUpdate::NetworkTokenDataUpdate { + network_token_requestor_reference_id: Some(network_token_requestor_ref_id), + network_token_locker_id: Some(token_pm_resp.payment_method_id), + network_token_payment_method_data: pm_network_token_data_encrypted.map(Into::into), + }; + let db = &*state.store; + let existing_pm = db + .find_payment_method( + &((&state).into()), + key_store, + &pm_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db"); + match existing_pm { + Ok(existing_pm) => { + db.update_payment_method( + &((&state).into()), + key_store, + existing_pm, + pm_update, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; + logger::debug!("Network token added to locker and payment method updated"); + Ok(true) + } + Err(err) => { + logger::debug!("Failed to fetch existing payment method for pm_id {:?} and error: {:?}", pm_id, err); + Ok(false) + } + } + } + Err(err) => { + logger::debug!("Network token added to locker failed {:?}", err); + Ok(false) + } + } +} + // need to discuss regarding the migration APIs for v2 #[cfg(all( feature = "v2", @@ -1134,6 +1226,7 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); let res = create_payment_method( + //return this state, &req, &customer_id, From 2e07dbeeeb09af9802c8152d885bcb2de8cc042f Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Thu, 17 Oct 2024 03:35:32 +0530 Subject: [PATCH 03/10] fix clippy --- crates/cards/src/validate.rs | 1 - .../router/src/core/payment_methods/cards.rs | 25 ++++--------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index c638503b2558..de4502698901 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -27,7 +27,6 @@ pub struct CardNumberValidationErr(&'static str); /// Card number #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct CardNumber(StrongSecret); -pub struct NetworkToken(CardNumber); //will take this up in diff pr impl CardNumber { pub fn get_card_isin(&self) -> String { diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 7477cfbb6dca..97cbe0ccdd76 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -508,7 +508,7 @@ pub async fn migrate_payment_method( let payment_method_response = match resp { services::ApplicationResponse::Json(response) => response, _ => Err(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch the payment method response")?, // Handle the case where the response is not what you expect + .attach_printable("Failed to fetch the payment method response")?, }; let pm_id = payment_method_response.payment_method_id.clone(); @@ -590,15 +590,9 @@ pub async fn populate_bin_details_for_masked_card( Err(_) => { utils::when(!skip_card_expiry_validation, || { Err(report!(errors::ApiErrorResponse::InvalidRequestData { - message: format!("Invalid card expiry",) + message: "Invalid card expiry".to_string(), })) })? - - // if !skip_card_expiry_validation { - // Err(errors::ApiErrorResponse::InvalidRequestData { - // message: "Invalid card expiry".to_string(), - // })? //use when - // } } }; @@ -809,14 +803,6 @@ pub async fn skip_locker_call_and_migrate_payment_method( let customer_id = req.customer_id.clone().get_required_value("customer_id")?; // // In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details. - // let connector_mandate_details_req = req - // .connector_mandate_details - // .clone() - // .get_required_value("connector mandate details")?; - - // let connector_mandate_details = serde_json::to_value(&connector_mandate_details_req) - // .change_context(errors::ApiErrorResponse::InternalServerError) - // .attach_printable("Failed to parse connector mandate details")?; let connector_mandate_details = if should_require_connector_mandate_details { // If `should_require_connector_mandate_details` is true, we must extract it @@ -832,7 +818,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( ) } else { // If not required, serialize only if present in the request, else handle it (e.g., return null or skip) - let connector_mandate_details = req + req .connector_mandate_details .clone() .map(|connector_mandate_details_req| { @@ -840,8 +826,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to parse connector mandate details") }) - .transpose()?; - connector_mandate_details + .transpose()? }; let payment_method_billing_address: Option>> = req @@ -953,6 +938,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( not(feature = "payment_methods_v2"), not(feature = "customer_v2") ))] +#[allow(clippy::too_many_arguments)] pub async fn save_network_token_and_update_payment_method( state: routes::SessionState, req: &api::PaymentMethodMigrate, @@ -1226,7 +1212,6 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); let res = create_payment_method( - //return this state, &req, &customer_id, From d2c643f629cdce60e3fea79a23c78a33346ac9d6 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Mon, 21 Oct 2024 03:17:53 +0530 Subject: [PATCH 04/10] code refactoring --- crates/api_models/src/payment_methods.rs | 58 ++++-- .../router/src/core/payment_methods/cards.rs | 184 +++++++++++------- .../src/core/payment_methods/migration.rs | 16 +- 3 files changed, 169 insertions(+), 89 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 96e1d4dfc3b4..3ecbb39dcbdf 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use cards::CardNumber; use common_utils::{ - consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, id_type, link_utils, pii, types::{MinorUnit, Percentage, Surcharge} + consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, errors, ext_traits::OptionExt, id_type, link_utils, pii, types::{MinorUnit, Percentage, Surcharge} }; use masking::PeekInterface; use serde::de; @@ -281,11 +281,20 @@ pub struct PaymentMethodMigrate { #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct PaymentMethodMigrateResponse { + //payment method response when payment method entry is created pub payment_method_response: PaymentMethodResponse, + //card data migration status pub card_migrated: Option, + //network token data migration status pub network_token_migrated: Option, + + //connector mandate details migration status + pub connector_mandate_details_migrated: Option, + + //network transaction id migration status + pub network_transaction_id_migrated: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -2129,7 +2138,7 @@ pub struct PaymentMethodRecord { pub payment_method: Option, pub payment_method_type: Option, pub nick_name: masking::Secret, - pub payment_instrument_id: masking::Secret, + pub payment_instrument_id: Option>, pub card_number_masked: masking::Secret, pub card_expiry_month: masking::Secret, pub card_expiry_year: masking::Secret, @@ -2145,7 +2154,7 @@ pub struct PaymentMethodRecord { pub billing_address_line2: Option>, pub billing_address_line3: Option>, pub raw_card_number: Option>, - pub merchant_connector_id: id_type::MerchantConnectorAccountId, + pub merchant_connector_id: Option, pub original_transaction_amount: Option, pub original_transaction_currency: Option, pub line_number: Option, @@ -2172,7 +2181,8 @@ pub struct PaymentMethodMigrationResponse { pub card_number_masked: Option>, pub card_migrated: Option, pub network_token_migrated: Option, - + pub connector_mandate_details_migrated: Option, + pub network_transaction_id_migrated: Option, } #[derive(Debug, Default, serde::Serialize)] @@ -2204,6 +2214,8 @@ impl From for PaymentMethodMigrationResponse line_number: record.line_number, card_migrated: res.card_migrated, network_token_migrated: res.network_token_migrated, + connector_mandate_details_migrated: res.connector_mandate_details_migrated, + network_transaction_id_migrated: res.network_transaction_id_migrated, }, Err(e) => Self { customer_id: Some(record.customer_id), @@ -2243,19 +2255,27 @@ impl From for PaymentMethodMigrationResponse } } -impl From for PaymentMethodMigrate { - fn from(record: PaymentMethodRecord) -> Self { - let mut mandate_reference = HashMap::new(); - mandate_reference.insert( - record.merchant_connector_id, - PaymentsMandateReferenceRecord { - connector_mandate_id: record.payment_instrument_id.peek().to_string(), - payment_method_type: record.payment_method_type, - original_payment_authorized_amount: record.original_transaction_amount, - original_payment_authorized_currency: record.original_transaction_currency, - }, - ); - Self { +impl TryFrom for PaymentMethodMigrate { + type Error = error_stack::Report; + fn try_from( + record: PaymentMethodRecord, + ) -> Result { + + // if payment instrument id is present then only construct this + let connector_mandate_details = if record.payment_instrument_id.is_some() { + Some(PaymentsMandateReference(HashMap::from([ + (record.merchant_connector_id.get_required_value("merchant_connector_id")?, + PaymentsMandateReferenceRecord { + connector_mandate_id: record.payment_instrument_id.get_required_value("payment_instrument_id")?.peek().to_string(), + payment_method_type: record.payment_method_type, + original_payment_authorized_amount: record.original_transaction_amount, + original_payment_authorized_currency: record.original_transaction_currency, + }) + ]))) + } else { + None + }; + Ok(Self { merchant_id: record.merchant_id, customer_id: Some(record.customer_id), card: Some(MigrateCardDetail { @@ -2306,7 +2326,7 @@ impl From for PaymentMethodMigrate { }), email: record.email, }), - connector_mandate_details: Some(PaymentsMandateReference(mandate_reference)), + connector_mandate_details, metadata: None, payment_method_issuer_code: None, card_network: None, @@ -2317,7 +2337,7 @@ impl From for PaymentMethodMigrate { payment_method_data: None, network_transaction_id: record.original_transaction_id, skip_card_expiry_validation: record.skip_card_expiry_validation, - } + }) } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 97cbe0ccdd76..839630cde036 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -395,6 +395,13 @@ pub async fn get_or_insert_payment_method( todo!() } +pub struct RecordMigrationStatus { + pub card_migrated: Option, + pub network_token_migrated: Option, + pub connector_mandate_details_migrated: Option, + pub network_transaction_migrated: Option, +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -412,7 +419,7 @@ pub async fn migrate_payment_method( let card_number_validation_result = cards::CardNumber::from_str(card_details.card_number.peek()); - let skip_card_expiry_validation = req.skip_card_expiry_validation.unwrap_or(false); //here if `skip_card_expiry_validation` is not present in the request it will be set to false + let skip_card_expiry_validation = req.skip_card_expiry_validation.unwrap_or_default(); //if skip_card_expiry_validation is true, then card expiry validation will be skipped, this is because network tokens can still be migrated even if the card is expired let card_bin_details = populate_bin_details_for_masked_card( card_details, @@ -442,6 +449,13 @@ pub async fn migrate_payment_method( let should_require_connector_mandate_details = req.network_token.is_none(); + let mut migration_status = RecordMigrationStatus { + card_migrated: None, + network_token_migrated: None, + connector_mandate_details_migrated: None, + network_transaction_migrated: None, + }; + let resp = match card_number_validation_result { Ok(card_number) => { let payment_method_create_request = @@ -460,51 +474,55 @@ pub async fn migrate_payment_method( // but will update the payment_method_data with bin details and migrate connector mandate details if present. if validation_result.is_err() { logger::debug!("Skip storing card in locker as skip_card_expiry_validation is {} and card is expired",skip_card_expiry_validation); - skip_locker_call_and_migrate_payment_method( - state.clone(), - &req, - merchant_id.to_owned(), - key_store, - merchant_account, - card_bin_details.clone(), - should_require_connector_mandate_details, //when there is no card and no network token, should_require_connector_mandate_details is set to true and connector mandate details are required - ) - .await + skip_locker_call_and_migrate_payment_method( + state.clone(), + &req, + merchant_id.to_owned(), + key_store, + merchant_account, + card_bin_details.clone(), + should_require_connector_mandate_details, //when there is no card and no network token, should_require_connector_mandate_details is set to true and connector mandate details are required + &mut migration_status, + ) + .await? } else { logger::debug!("Storing the card in locker and migrating the payment method"); + get_client_secret_or_add_payment_method_for_migrate_api( + &state, + payment_method_create_request, + merchant_account, + key_store, + &mut migration_status, + ) + .await? + } + } else { + logger::debug!("Storing the card in locker and migrating the payment method"); get_client_secret_or_add_payment_method_for_migrate_api( &state, payment_method_create_request, merchant_account, key_store, + &mut migration_status, ) - .await - } - } else { - logger::debug!("Storing the card in locker and migrating the payment method"); - get_client_secret_or_add_payment_method_for_migrate_api( - &state, - payment_method_create_request, - merchant_account, - key_store, - ) - .await + .await? } } Err(card_validation_error) => { logger::debug!("Card number to be migrated is invalid, skip saving in locker {card_validation_error}"); - skip_locker_call_and_migrate_payment_method( - state.clone(), - &req, - merchant_id.to_owned(), - key_store, - merchant_account, - card_bin_details.clone(), - should_require_connector_mandate_details, - ) - .await + skip_locker_call_and_migrate_payment_method( + state.clone(), + &req, + merchant_id.to_owned(), + key_store, + merchant_account, + card_bin_details.clone(), + should_require_connector_mandate_details, + &mut migration_status, + ) + .await? } - }?; + }; let payment_method_response = match resp { services::ApplicationResponse::Json(response) => response, _ => Err(errors::ApiErrorResponse::InternalServerError) @@ -515,12 +533,12 @@ pub async fn migrate_payment_method( let network_token = req.network_token.clone(); - let network_token_migrated = network_token.clone().map(|network_token| { + let network_token_validation = network_token.clone().map(|network_token| { cards::CardNumber::from_str(network_token.network_token_data.network_token_number.peek()) .map_err(|_| false) }); // network_token_migrated is true if network_token_number passes the validation and will be migrated to locker - let v2 = match network_token_migrated { + migration_status.network_token_migrated = match network_token_validation { Some(Ok(network_token)) => { logger::debug!("Network token to be migrated is valid, saving in locker"); let network_token_details = req @@ -543,8 +561,9 @@ pub async fn migrate_payment_method( pm_id, ) .await + .map_err(|err| logger::error!(?err, "Failed to save network token in locker")) .ok() - .unwrap_or(false), + .unwrap_or_default(), ) } Some(Err(_)) => { @@ -560,8 +579,10 @@ pub async fn migrate_payment_method( Ok(services::ApplicationResponse::Json( api::PaymentMethodMigrateResponse { payment_method_response, - card_migrated: Some(true), - network_token_migrated: v2, + card_migrated: migration_status.card_migrated, + network_token_migrated: migration_status.network_token_migrated, + connector_mandate_details_migrated: migration_status.connector_mandate_details_migrated, + network_transaction_id_migrated: migration_status.network_transaction_migrated, }, )) } @@ -587,13 +608,11 @@ pub async fn populate_bin_details_for_masked_card( match validate { Ok(_) => (), - Err(_) => { - utils::when(!skip_card_expiry_validation, || { - Err(report!(errors::ApiErrorResponse::InvalidRequestData { - message: "Invalid card expiry".to_string(), - })) - })? - } + Err(_) => utils::when(!skip_card_expiry_validation, || { + Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid card expiry".to_string(), + })) + })?, }; let card_number = card_details.card_number.clone(); @@ -798,6 +817,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( merchant_account: &domain::MerchantAccount, card: api_models::payment_methods::CardDetailFromLocker, should_require_connector_mandate_details: bool, + migration_status: &mut RecordMigrationStatus, ) -> errors::RouterResponse { let db = &*state.store; let customer_id = req.customer_id.clone().get_required_value("customer_id")?; @@ -818,8 +838,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( ) } else { // If not required, serialize only if present in the request, else handle it (e.g., return null or skip) - req - .connector_mandate_details + req.connector_mandate_details .clone() .map(|connector_mandate_details_req| { serde_json::to_value(&connector_mandate_details_req) @@ -864,6 +883,12 @@ pub async fn skip_locker_call_and_migrate_payment_method( let network_transaction_id = req.network_transaction_id.clone(); + migration_status.network_transaction_migrated = network_transaction_id.clone().map(|_| true); + migration_status.connector_mandate_details_migrated = connector_mandate_details + .clone() + .map(|_| true) + .or_else(|| req.connector_mandate_details.clone().map(|_| false)); + let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); let current_time = common_utils::date_time::now(); @@ -1015,27 +1040,27 @@ pub async fn save_network_token_and_update_payment_method( ) .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db"); - match existing_pm { - Ok(existing_pm) => { - db.update_payment_method( - &((&state).into()), - key_store, - existing_pm, - pm_update, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; - logger::debug!("Network token added to locker and payment method updated"); - Ok(true) - } - Err(err) => { - logger::debug!("Failed to fetch existing payment method for pm_id {:?} and error: {:?}", pm_id, err); - Ok(false) - } - } + .attach_printable(format!( + "Failed to fetch payment method for existing pm_id: {:?} in db", + pm_id + ))?; + + db.update_payment_method( + &((&state).into()), + key_store, + existing_pm, + pm_update, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable(format!( + "Failed to update payment method for existing pm_id: {:?} in db", + pm_id + ))?; + + logger::debug!("Network token added to locker and payment method updated"); + Ok(true) } Err(err) => { logger::debug!("Network token added to locker failed {:?}", err); @@ -1175,6 +1200,7 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( req: api::PaymentMethodCreate, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, + migration_status: &mut RecordMigrationStatus, ) -> errors::RouterResponse { let merchant_id = merchant_account.get_id(); let customer_id = req.customer_id.clone().get_required_value("customer_id")?; @@ -1200,12 +1226,18 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( .transpose() .change_context(errors::ApiErrorResponse::InternalServerError)?; + migration_status.connector_mandate_details_migrated = connector_mandate_details + .clone() + .map(|_| true) + .or_else(|| req.connector_mandate_details.clone().map(|_| false)); + if condition { Box::pin(add_payment_method_for_migrate_api( state, req, merchant_account, key_store, + migration_status, )) .await } else { @@ -1234,6 +1266,8 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( ) .await?; + migration_status.card_migrated = Some(false); //card is not migrated in this case + if res.status == enums::PaymentMethodStatus::AwaitingData { add_payment_method_status_update_task( &*state.store, @@ -1758,6 +1792,7 @@ pub async fn add_payment_method_for_migrate_api( req: api::PaymentMethodCreate, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, + migration_status: &mut RecordMigrationStatus, ) -> errors::RouterResponse { req.validate()?; let db = &*state.store; @@ -1780,6 +1815,16 @@ pub async fn add_payment_method_for_migrate_api( .transpose() .change_context(errors::ApiErrorResponse::InternalServerError)?; + let network_transaction_id = req.network_transaction_id.clone(); + + migration_status.network_transaction_migrated = network_transaction_id.clone().map(|_| true); + migration_status.connector_mandate_details_migrated = connector_mandate_details + .clone() + .map(|_| true) + .or_else(|| req.connector_mandate_details.clone().map(|_| false)); + + + let response = match payment_method { #[cfg(feature = "payouts")] api_enums::PaymentMethod::BankTransfer => match req.bank_transfer.clone() { @@ -1839,6 +1884,7 @@ pub async fn add_payment_method_for_migrate_api( let (mut resp, duplication_check) = response?; + migration_status.card_migrated = Some(true); //card is saved to locker match duplication_check { Some(duplication_check) => match duplication_check { payment_methods::DataDuplicationCheck::Duplicated => { @@ -1989,7 +2035,7 @@ pub async fn add_payment_method_for_migrate_api( None, locker_id, connector_mandate_details, - req.network_transaction_id.clone(), + network_transaction_id, merchant_account.storage_scheme, payment_method_billing_address.map(Into::into), None, diff --git a/crates/router/src/core/payment_methods/migration.rs b/crates/router/src/core/payment_methods/migration.rs index d548d03ab47c..9a3afbbcd778 100644 --- a/crates/router/src/core/payment_methods/migration.rs +++ b/crates/router/src/core/payment_methods/migration.rs @@ -1,6 +1,7 @@ use actix_multipart::form::{bytes::Bytes, MultipartForm}; use api_models::payment_methods::{PaymentMethodMigrationResponse, PaymentMethodRecord}; use csv::Reader; +use error_stack::ResultExt; use rdkafka::message::ToBytes; use crate::{ @@ -18,9 +19,22 @@ pub async fn migrate_payment_methods( ) -> errors::RouterResponse> { let mut result = Vec::new(); for record in payment_methods { + let req = api::PaymentMethodMigrate::try_from(record.clone()) + .map_err(|err| errors::ApiErrorResponse::InvalidRequestData{ message: format!("error: {:?}", err) }) + .attach_printable("record deserialization failed"); + match req { + Ok(_) => (), + Err(e) => { + result.push(PaymentMethodMigrationResponse::from(( + Err(e.to_string()), + record, + ))); + continue; + } + }; let res = migrate_payment_method( state.clone(), - api::PaymentMethodMigrate::from(record.clone()), + req?, merchant_id, merchant_account, key_store, From fa7b49d3403588940960007c0b33db24a5ed04ce Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Thu, 24 Oct 2024 13:34:36 +0530 Subject: [PATCH 05/10] code refactoring --- crates/api_models/src/payment_methods.rs | 57 +++++++++++++------ .../router/src/core/payment_methods/cards.rs | 12 ++-- .../src/core/payment_methods/migration.rs | 28 +++------ crates/router/src/routes/payment_methods.rs | 8 ++- 4 files changed, 60 insertions(+), 45 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 3ecbb39dcbdf..cc76300e4e44 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -4,7 +4,12 @@ use std::str::FromStr; use cards::CardNumber; use common_utils::{ - consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, errors, ext_traits::OptionExt, id_type, link_utils, pii, types::{MinorUnit, Percentage, Surcharge} + consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, + crypto::OptionalEncryptableName, + errors, + ext_traits::OptionExt, + id_type, link_utils, pii, + types::{MinorUnit, Percentage, Surcharge}, }; use masking::PeekInterface; use serde::de; @@ -2134,7 +2139,7 @@ pub struct PaymentMethodRecord { pub email: Option, pub phone: Option>, pub phone_country_code: Option, - pub merchant_id: id_type::MerchantId, + pub merchant_id: Option, pub payment_method: Option, pub payment_method_type: Option, pub nick_name: masking::Secret, @@ -2255,28 +2260,43 @@ impl From for PaymentMethodMigrationResponse } } -impl TryFrom for PaymentMethodMigrate { +impl + TryFrom<( + PaymentMethodRecord, + id_type::MerchantId, + Option, + )> for PaymentMethodMigrate +{ type Error = error_stack::Report; fn try_from( - record: PaymentMethodRecord, + item: ( + PaymentMethodRecord, + id_type::MerchantId, + Option, + ), ) -> Result { - + let (record, merchant_id, mca_id) = item; + // if payment instrument id is present then only construct this let connector_mandate_details = if record.payment_instrument_id.is_some() { - Some(PaymentsMandateReference(HashMap::from([ - (record.merchant_connector_id.get_required_value("merchant_connector_id")?, - PaymentsMandateReferenceRecord { - connector_mandate_id: record.payment_instrument_id.get_required_value("payment_instrument_id")?.peek().to_string(), - payment_method_type: record.payment_method_type, - original_payment_authorized_amount: record.original_transaction_amount, - original_payment_authorized_currency: record.original_transaction_currency, - }) - ]))) + Some(PaymentsMandateReference(HashMap::from([( + mca_id.get_required_value("merchant_connector_id")?, + PaymentsMandateReferenceRecord { + connector_mandate_id: record + .payment_instrument_id + .get_required_value("payment_instrument_id")? + .peek() + .to_string(), + payment_method_type: record.payment_method_type, + original_payment_authorized_amount: record.original_transaction_amount, + original_payment_authorized_currency: record.original_transaction_currency, + }, + )]))) } else { None }; Ok(Self { - merchant_id: record.merchant_id, + merchant_id, customer_id: Some(record.customer_id), card: Some(MigrateCardDetail { card_number: record.raw_card_number.unwrap_or(record.card_number_masked), @@ -2342,11 +2362,12 @@ impl TryFrom for PaymentMethodMigrate { } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -impl From for customers::CustomerRequest { - fn from(record: PaymentMethodRecord) -> Self { +impl From<(PaymentMethodRecord, id_type::MerchantId)> for customers::CustomerRequest { + fn from(value: (PaymentMethodRecord, id_type::MerchantId)) -> Self { + let (record, merchant_id) = value; Self { customer_id: Some(record.customer_id), - merchant_id: record.merchant_id, + merchant_id, name: record.name, email: record.email, phone: record.phone, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 839630cde036..a68f025f61e9 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1226,10 +1226,7 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( .transpose() .change_context(errors::ApiErrorResponse::InternalServerError)?; - migration_status.connector_mandate_details_migrated = connector_mandate_details - .clone() - .map(|_| true) - .or_else(|| req.connector_mandate_details.clone().map(|_| false)); + if condition { Box::pin(add_payment_method_for_migrate_api( @@ -1242,6 +1239,7 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( .await } else { let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); + let res = create_payment_method( state, @@ -1254,7 +1252,7 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( None, None, key_store, - connector_mandate_details, + connector_mandate_details.clone(), Some(enums::PaymentMethodStatus::AwaitingData), None, merchant_account.storage_scheme, @@ -1265,6 +1263,10 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( None, ) .await?; + migration_status.connector_mandate_details_migrated = connector_mandate_details + .clone() + .map(|_| true) + .or_else(|| req.connector_mandate_details.clone().map(|_| false)); migration_status.card_migrated = Some(false); //card is not migrated in this case diff --git a/crates/router/src/core/payment_methods/migration.rs b/crates/router/src/core/payment_methods/migration.rs index 9a3afbbcd778..e5693622d8fd 100644 --- a/crates/router/src/core/payment_methods/migration.rs +++ b/crates/router/src/core/payment_methods/migration.rs @@ -1,4 +1,4 @@ -use actix_multipart::form::{bytes::Bytes, MultipartForm}; +use actix_multipart::form::{bytes::Bytes, text::Text, MultipartForm}; use api_models::payment_methods::{PaymentMethodMigrationResponse, PaymentMethodRecord}; use csv::Reader; use error_stack::ResultExt; @@ -16,10 +16,11 @@ pub async fn migrate_payment_methods( merchant_id: &common_utils::id_type::MerchantId, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, + mca_id: Option, ) -> errors::RouterResponse> { let mut result = Vec::new(); for record in payment_methods { - let req = api::PaymentMethodMigrate::try_from(record.clone()) + let req = api::PaymentMethodMigrate::try_from((record.clone(), merchant_id.clone(), mca_id.clone())) .map_err(|err| errors::ApiErrorResponse::InvalidRequestData{ message: format!("error: {:?}", err) }) .attach_printable("record deserialization failed"); match req { @@ -56,6 +57,8 @@ pub async fn migrate_payment_methods( pub struct PaymentMethodsMigrateForm { #[multipart(limit = "1MB")] pub file: Bytes, + pub merchant_id: Text, + pub mca_id: Text>, } fn parse_csv(data: &[u8]) -> csv::Result> { @@ -72,26 +75,13 @@ fn parse_csv(data: &[u8]) -> csv::Result> { } pub fn get_payment_method_records( form: PaymentMethodsMigrateForm, -) -> Result<(common_utils::id_type::MerchantId, Vec), errors::ApiErrorResponse> +) -> Result<(common_utils::id_type::MerchantId, Vec, Option), errors::ApiErrorResponse> { match parse_csv(form.file.data.to_bytes()) { Ok(records) => { - if let Some(first_record) = records.first() { - if records - .iter() - .all(|merchant_id| merchant_id.merchant_id == first_record.merchant_id) - { - Ok((first_record.merchant_id.clone(), records)) - } else { - Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Only one merchant id can be updated at a time".to_string(), - }) - } - } else { - Err(errors::ApiErrorResponse::PreconditionFailed { - message: "No records found".to_string(), - }) - } + let merchant_id = form.merchant_id.clone(); + let mca_id = form.mca_id.clone(); + Ok((merchant_id.clone(), records, mca_id)) } Err(e) => Err(errors::ApiErrorResponse::PreconditionFailed { message: e.to_string(), diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 3e3aada154fa..556930f2c6f4 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -304,8 +304,8 @@ pub async fn migrate_payment_methods( MultipartForm(form): MultipartForm, ) -> HttpResponse { let flow = Flow::PaymentMethodsMigrate; - let (merchant_id, records) = match migration::get_payment_method_records(form) { - Ok((merchant_id, records)) => (merchant_id, records), + let (merchant_id, records, mca_id) = match migration::get_payment_method_records(form) { + Ok((merchant_id, records, mca_id)) => (merchant_id, records, mca_id), Err(e) => return api::log_and_return_error_response(e.into()), }; Box::pin(api::server_wrap( @@ -315,6 +315,7 @@ pub async fn migrate_payment_methods( records, |state, _, req, _| { let merchant_id = merchant_id.clone(); + let mca_id = mca_id.clone(); async move { let (key_store, merchant_account) = get_merchant_account(&state, &merchant_id).await?; @@ -322,7 +323,7 @@ pub async fn migrate_payment_methods( customers::migrate_customers( state.clone(), req.iter() - .map(|e| CustomerRequest::from(e.clone())) + .map(|e| CustomerRequest::from((e.clone(), merchant_id.clone()))) .collect(), merchant_account.clone(), key_store.clone(), @@ -335,6 +336,7 @@ pub async fn migrate_payment_methods( &merchant_id, &merchant_account, &key_store, + mca_id, )) .await } From f3eaff020afd4d30b98e8f324e58cd9ca8beeac0 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Mon, 4 Nov 2024 16:58:23 +0530 Subject: [PATCH 06/10] add status builder for inplace update --- .../router/src/core/payment_methods/cards.rs | 141 +++++++++--------- .../src/core/payment_methods/migration.rs | 52 +++++++ 2 files changed, 120 insertions(+), 73 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index a68f025f61e9..13746677bd4b 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4,6 +4,7 @@ use std::{ str::FromStr, }; +use super::migration::RecordMigrationStatusBuilder; #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -395,12 +396,6 @@ pub async fn get_or_insert_payment_method( todo!() } -pub struct RecordMigrationStatus { - pub card_migrated: Option, - pub network_token_migrated: Option, - pub connector_mandate_details_migrated: Option, - pub network_transaction_migrated: Option, -} #[cfg(all( any(feature = "v1", feature = "v2"), @@ -449,12 +444,7 @@ pub async fn migrate_payment_method( let should_require_connector_mandate_details = req.network_token.is_none(); - let mut migration_status = RecordMigrationStatus { - card_migrated: None, - network_token_migrated: None, - connector_mandate_details_migrated: None, - network_transaction_migrated: None, - }; + let mut migration_status = RecordMigrationStatusBuilder::new(); let resp = match card_number_validation_result { Ok(card_number) => { @@ -474,30 +464,19 @@ pub async fn migrate_payment_method( // but will update the payment_method_data with bin details and migrate connector mandate details if present. if validation_result.is_err() { logger::debug!("Skip storing card in locker as skip_card_expiry_validation is {} and card is expired",skip_card_expiry_validation); - skip_locker_call_and_migrate_payment_method( - state.clone(), - &req, - merchant_id.to_owned(), - key_store, - merchant_account, - card_bin_details.clone(), - should_require_connector_mandate_details, //when there is no card and no network token, should_require_connector_mandate_details is set to true and connector mandate details are required - &mut migration_status, - ) - .await? + skip_locker_call_and_migrate_payment_method( + state.clone(), + &req, + merchant_id.to_owned(), + key_store, + merchant_account, + card_bin_details.clone(), + should_require_connector_mandate_details, //when there is no card and no network token, should_require_connector_mandate_details is set to true and connector mandate details are required + &mut migration_status, + ) + .await? } else { logger::debug!("Storing the card in locker and migrating the payment method"); - get_client_secret_or_add_payment_method_for_migrate_api( - &state, - payment_method_create_request, - merchant_account, - key_store, - &mut migration_status, - ) - .await? - } - } else { - logger::debug!("Storing the card in locker and migrating the payment method"); get_client_secret_or_add_payment_method_for_migrate_api( &state, payment_method_create_request, @@ -506,21 +485,32 @@ pub async fn migrate_payment_method( &mut migration_status, ) .await? - } - } - Err(card_validation_error) => { - logger::debug!("Card number to be migrated is invalid, skip saving in locker {card_validation_error}"); - skip_locker_call_and_migrate_payment_method( - state.clone(), - &req, - merchant_id.to_owned(), - key_store, + } + } else { + logger::debug!("Storing the card in locker and migrating the payment method"); + get_client_secret_or_add_payment_method_for_migrate_api( + &state, + payment_method_create_request, merchant_account, - card_bin_details.clone(), - should_require_connector_mandate_details, + key_store, &mut migration_status, ) .await? + } + } + Err(card_validation_error) => { + logger::debug!("Card number to be migrated is invalid, skip saving in locker {card_validation_error}"); + skip_locker_call_and_migrate_payment_method( + state.clone(), + &req, + merchant_id.to_owned(), + key_store, + merchant_account, + card_bin_details.clone(), + should_require_connector_mandate_details, + &mut migration_status, + ) + .await? } }; let payment_method_response = match resp { @@ -538,7 +528,7 @@ pub async fn migrate_payment_method( .map_err(|_| false) }); // network_token_migrated is true if network_token_number passes the validation and will be migrated to locker - migration_status.network_token_migrated = match network_token_validation { + let network_token_migrated = match network_token_validation { Some(Ok(network_token)) => { logger::debug!("Network token to be migrated is valid, saving in locker"); let network_token_details = req @@ -575,14 +565,16 @@ pub async fn migrate_payment_method( None } }; + migration_status.network_token_migrated(network_token_migrated); + let migrate_status = migration_status.build(); Ok(services::ApplicationResponse::Json( api::PaymentMethodMigrateResponse { payment_method_response, - card_migrated: migration_status.card_migrated, - network_token_migrated: migration_status.network_token_migrated, - connector_mandate_details_migrated: migration_status.connector_mandate_details_migrated, - network_transaction_id_migrated: migration_status.network_transaction_migrated, + card_migrated: migrate_status.card_migrated, + network_token_migrated: migrate_status.network_token_migrated, + connector_mandate_details_migrated: migrate_status.connector_mandate_details_migrated, + network_transaction_id_migrated: migrate_status.network_transaction_migrated, }, )) } @@ -817,7 +809,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( merchant_account: &domain::MerchantAccount, card: api_models::payment_methods::CardDetailFromLocker, should_require_connector_mandate_details: bool, - migration_status: &mut RecordMigrationStatus, + migration_status: &mut RecordMigrationStatusBuilder, ) -> errors::RouterResponse { let db = &*state.store; let customer_id = req.customer_id.clone().get_required_value("customer_id")?; @@ -883,11 +875,14 @@ pub async fn skip_locker_call_and_migrate_payment_method( let network_transaction_id = req.network_transaction_id.clone(); - migration_status.network_transaction_migrated = network_transaction_id.clone().map(|_| true); - migration_status.connector_mandate_details_migrated = connector_mandate_details - .clone() - .map(|_| true) - .or_else(|| req.connector_mandate_details.clone().map(|_| false)); + migration_status.network_transaction_id_migrated(network_transaction_id.clone().map(|_| true)); + + migration_status.connector_mandate_details_migrated( + connector_mandate_details + .clone() + .map(|_| true) + .or_else(|| req.connector_mandate_details.clone().map(|_| false)), + ); let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); @@ -1200,7 +1195,7 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( req: api::PaymentMethodCreate, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, - migration_status: &mut RecordMigrationStatus, + migration_status: &mut RecordMigrationStatusBuilder, ) -> errors::RouterResponse { let merchant_id = merchant_account.get_id(); let customer_id = req.customer_id.clone().get_required_value("customer_id")?; @@ -1226,8 +1221,6 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( .transpose() .change_context(errors::ApiErrorResponse::InternalServerError)?; - - if condition { Box::pin(add_payment_method_for_migrate_api( state, @@ -1239,7 +1232,6 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( .await } else { let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); - let res = create_payment_method( state, @@ -1263,12 +1255,14 @@ pub async fn get_client_secret_or_add_payment_method_for_migrate_api( None, ) .await?; - migration_status.connector_mandate_details_migrated = connector_mandate_details - .clone() - .map(|_| true) - .or_else(|| req.connector_mandate_details.clone().map(|_| false)); + migration_status.connector_mandate_details_migrated( + connector_mandate_details + .clone() + .map(|_| true) + .or_else(|| req.connector_mandate_details.clone().map(|_| false)), + ); - migration_status.card_migrated = Some(false); //card is not migrated in this case + migration_status.card_migrated(false); //card is not migrated in this case if res.status == enums::PaymentMethodStatus::AwaitingData { add_payment_method_status_update_task( @@ -1794,7 +1788,7 @@ pub async fn add_payment_method_for_migrate_api( req: api::PaymentMethodCreate, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, - migration_status: &mut RecordMigrationStatus, + migration_status: &mut RecordMigrationStatusBuilder, ) -> errors::RouterResponse { req.validate()?; let db = &*state.store; @@ -1819,13 +1813,14 @@ pub async fn add_payment_method_for_migrate_api( let network_transaction_id = req.network_transaction_id.clone(); - migration_status.network_transaction_migrated = network_transaction_id.clone().map(|_| true); - migration_status.connector_mandate_details_migrated = connector_mandate_details - .clone() - .map(|_| true) - .or_else(|| req.connector_mandate_details.clone().map(|_| false)); - + migration_status.network_transaction_id_migrated(network_transaction_id.clone().map(|_| true)); + migration_status.connector_mandate_details_migrated( + connector_mandate_details + .clone() + .map(|_| true) + .or_else(|| req.connector_mandate_details.clone().map(|_| false)), + ); let response = match payment_method { #[cfg(feature = "payouts")] @@ -1886,7 +1881,7 @@ pub async fn add_payment_method_for_migrate_api( let (mut resp, duplication_check) = response?; - migration_status.card_migrated = Some(true); //card is saved to locker + migration_status.card_migrated(true); //card is saved to locker match duplication_check { Some(duplication_check) => match duplication_check { payment_methods::DataDuplicationCheck::Duplicated => { diff --git a/crates/router/src/core/payment_methods/migration.rs b/crates/router/src/core/payment_methods/migration.rs index e5693622d8fd..867321dfdf11 100644 --- a/crates/router/src/core/payment_methods/migration.rs +++ b/crates/router/src/core/payment_methods/migration.rs @@ -88,3 +88,55 @@ pub fn get_payment_method_records( }), } } + +#[derive(Debug)] +pub struct RecordMigrationStatus { + pub card_migrated: Option, + pub network_token_migrated: Option, + pub connector_mandate_details_migrated: Option, + pub network_transaction_migrated: Option, +} + +#[derive(Debug)] +pub struct RecordMigrationStatusBuilder { + pub card_migrated: Option, + pub network_token_migrated: Option, + pub connector_mandate_details_migrated: Option, + pub network_transaction_migrated: Option, +} + +impl RecordMigrationStatusBuilder{ + pub fn new() -> Self { + Self { + card_migrated: None, + network_token_migrated: None, + connector_mandate_details_migrated: None, + network_transaction_migrated: None, + } + } + + pub fn card_migrated(&mut self, card_migrated: bool) { + self.card_migrated = Some(card_migrated); + } + + pub fn network_token_migrated(&mut self, network_token_migrated: Option) { + self.network_token_migrated = network_token_migrated; + } + + pub fn connector_mandate_details_migrated(&mut self, connector_mandate_details_migrated: Option) { + self.connector_mandate_details_migrated = connector_mandate_details_migrated; + } + + pub fn network_transaction_id_migrated(&mut self, network_transaction_migrated: Option) { + self.network_transaction_migrated = network_transaction_migrated; + } + + pub fn build(self) -> RecordMigrationStatus { + RecordMigrationStatus { + card_migrated: self.card_migrated, + network_token_migrated: self.network_token_migrated, + connector_mandate_details_migrated: self.connector_mandate_details_migrated, + network_transaction_migrated: self.network_transaction_migrated, + } + } +} \ No newline at end of file From 5815105a5e09e99332afa7d1854a0f76962fed32 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:19:25 +0000 Subject: [PATCH 07/10] chore: run formatter --- crates/router/src/core/payment_methods/cards.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index bf4b1519f977..c6f0fdc850cf 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4,7 +4,6 @@ use std::{ str::FromStr, }; -use super::migration::RecordMigrationStatusBuilder; #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -58,9 +57,12 @@ use masking::Secret; use router_env::{instrument, metrics::add_attributes, tracing}; use strum::IntoEnumIterator; -use super::surcharge_decision_configs::{ - perform_surcharge_decision_management_for_payment_method_list, - perform_surcharge_decision_management_for_saved_cards, +use super::{ + migration::RecordMigrationStatusBuilder, + surcharge_decision_configs::{ + perform_surcharge_decision_management_for_payment_method_list, + perform_surcharge_decision_management_for_saved_cards, + }, }; #[cfg(all( any(feature = "v2", feature = "v1"), From 7e0cbdb7b7b6d81db5bc78c9fa9ebe988908349e Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Fri, 8 Nov 2024 16:29:46 +0530 Subject: [PATCH 08/10] resolve pr comments --- crates/router/src/core/payment_methods/cards.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index c6f0fdc850cf..503c983ecdf6 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -455,9 +455,13 @@ pub async fn migrate_payment_method( let network_token_validation = network_token.clone().map(|network_token| { cards::CardNumber::from_str(network_token.network_token_data.network_token_number.peek()) - .map_err(|_| false) - }); // network_token_migrated is true if network_token_number passes the validation and will be migrated to locker - + .map_err(|err| { + logger::error!("Network Token Validation Failed. {:?}", err); + false + }) + }); + + // network_token_migrated is true if network_token_number passes the validation and will be migrated to locker let network_token_migrated = match network_token_validation { Some(Ok(network_token)) => { logger::debug!("Network token to be migrated is valid, saving in locker"); @@ -486,8 +490,8 @@ pub async fn migrate_payment_method( .unwrap_or_default(), ) } - Some(Err(_)) => { - logger::debug!("Network token validation failed, not saving in locker"); + Some(Err(err)) => { + logger::debug!("Network token validation failed, not saving in locker {:?}", err); Some(false) } None => { From 473c75ad12ac5737c72f8e9c32fd5a11a8506923 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Mon, 11 Nov 2024 14:40:52 +0530 Subject: [PATCH 09/10] fix migration api response --- .../router/src/core/payment_methods/cards.rs | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 912c7bd34d6a..ea21fe2f8e6c 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -28,7 +28,7 @@ use common_utils::{ consts, crypto::{self, Encryptable}, encryption::Encryption, - ext_traits::{AsyncExt, BytesExt, Encode, StringExt, ValueExt}, + ext_traits::{AsyncExt, BytesExt, ConfigExt, Encode, StringExt, ValueExt}, generate_id, id_type, request::Request, type_name, @@ -55,6 +55,7 @@ use hyperswitch_domain_models::customer::CustomerUpdate; use kgraph_utils::transformers::IntoDirValue; use masking::Secret; use router_env::{instrument, metrics::add_attributes, tracing}; +use serde_json::json; use strum::IntoEnumIterator; use super::surcharge_decision_configs::{ @@ -492,7 +493,13 @@ pub async fn migrate_payment_method( "Network token validation failed, not saving in locker {:?}", err ); - Some(false) + network_token.clone().and_then(|val| { + (!val + .network_token_data + .network_token_number + .is_empty_after_trim()) + .then_some(false) + }) } None => { logger::debug!("Network token data is not available"); @@ -798,15 +805,6 @@ pub async fn skip_locker_call_and_migrate_payment_method( let network_transaction_id = req.network_transaction_id.clone(); - migration_status.network_transaction_id_migrated(network_transaction_id.clone().map(|_| true)); - - migration_status.connector_mandate_details_migrated( - connector_mandate_details - .clone() - .map(|_| true) - .or_else(|| req.connector_mandate_details.clone().map(|_| false)), - ); - let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); let current_time = common_utils::date_time::now(); @@ -826,11 +824,11 @@ pub async fn skip_locker_call_and_migrate_payment_method( scheme: req.card_network.clone().or(card.scheme.clone()), metadata: payment_method_metadata.map(Secret::new), payment_method_data: payment_method_data_encrypted.map(Into::into), - connector_mandate_details, + connector_mandate_details: connector_mandate_details.clone(), customer_acceptance: None, client_secret: None, status: enums::PaymentMethodStatus::Active, - network_transaction_id, + network_transaction_id: network_transaction_id.clone(), payment_method_issuer_code: None, accepted_currency: None, token: None, @@ -859,6 +857,21 @@ pub async fn skip_locker_call_and_migrate_payment_method( logger::debug!("Payment method inserted in db"); + migration_status.network_transaction_id_migrated( + network_transaction_id.and_then(|val| (!val.is_empty_after_trim()).then_some(true)), + ); + + migration_status.connector_mandate_details_migrated( + connector_mandate_details + .clone() + .and_then(|val| if val == json!({}) { None } else { Some(true) }) + .or_else(|| { + req.connector_mandate_details + .clone() + .and_then(|val| (!val.0.is_empty()).then_some(false)) + }), + ); + if customer.default_payment_method_id.is_none() && req.payment_method.is_some() { let _ = set_default_payment_method( &state, @@ -1183,8 +1196,12 @@ pub async fn get_client_secret_or_add_payment_method_for_migration( migration_status.connector_mandate_details_migrated( connector_mandate_details .clone() - .map(|_| true) - .or_else(|| req.connector_mandate_details.clone().map(|_| false)), + .and_then(|val| (val != json!({})).then_some(true)) + .or_else(|| { + req.connector_mandate_details + .clone() + .and_then(|val| (!val.0.is_empty()).then_some(false)) + }), ); migration_status.card_migrated(false); //card is not migrated in this case @@ -1739,15 +1756,6 @@ pub async fn save_migration_payment_method( let network_transaction_id = req.network_transaction_id.clone(); - migration_status.network_transaction_id_migrated(network_transaction_id.clone().map(|_| true)); - - migration_status.connector_mandate_details_migrated( - connector_mandate_details - .clone() - .map(|_| true) - .or_else(|| req.connector_mandate_details.clone().map(|_| false)), - ); - let response = match payment_method { #[cfg(feature = "payouts")] api_enums::PaymentMethod::BankTransfer => match req.bank_transfer.clone() { @@ -1807,7 +1815,6 @@ pub async fn save_migration_payment_method( let (mut resp, duplication_check) = response?; - migration_status.card_migrated(true); //card is saved to locker match duplication_check { Some(duplication_check) => match duplication_check { payment_methods::DataDuplicationCheck::Duplicated => { @@ -1957,8 +1964,8 @@ pub async fn save_migration_payment_method( pm_metadata.cloned(), None, locker_id, - connector_mandate_details, - network_transaction_id, + connector_mandate_details.clone(), + network_transaction_id.clone(), merchant_account.storage_scheme, payment_method_billing_address.map(Into::into), None, @@ -1971,6 +1978,20 @@ pub async fn save_migration_payment_method( } } + migration_status.card_migrated(true); + migration_status.network_transaction_id_migrated( + network_transaction_id.and_then(|val| (!val.is_empty_after_trim()).then_some(true)), + ); + + migration_status.connector_mandate_details_migrated( + connector_mandate_details + .and_then(|val| if val == json!({}) { None } else { Some(true) }) + .or_else(|| { + req.connector_mandate_details + .and_then(|val| (!val.0.is_empty()).then_some(false)) + }), + ); + Ok(services::ApplicationResponse::Json(resp)) } From c14b5ef01f85cb68fdd8a513e8b265bc681ed149 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Tue, 3 Dec 2024 16:12:35 +0530 Subject: [PATCH 10/10] code refactoring --- crates/router/src/core/payment_methods/cards.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 0ae81cbd0382..f07634060df6 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -382,7 +382,7 @@ pub async fn migrate_payment_method( key_store: &domain::MerchantKeyStore, ) -> errors::RouterResponse { let mut req = req; - let card_details = &req.card.clone().get_required_value("card")?; + let card_details = &req.card.get_required_value("card")?; let card_number_validation_result = cards::CardNumber::from_str(card_details.card_number.peek()); @@ -1178,8 +1178,8 @@ pub async fn get_client_secret_or_add_payment_method_for_migration( .and_then(|val| (!val.0.is_empty()).then_some(false)) }), ); - - migration_status.card_migrated(false); //card is not migrated in this case + //card is not migrated in this case + migration_status.card_migrated(false); if res.status == enums::PaymentMethodStatus::AwaitingData { add_payment_method_status_update_task(