From b88e259ccf8026f0d9b9701479044c5db10a2723 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Mon, 3 Jun 2024 19:00:03 +0530 Subject: [PATCH 01/30] refactor(core): Refactor customer pml for v2 --- crates/api_models/src/payment_methods.rs | 2 +- .../compatibility/stripe/customers/types.rs | 3 +- crates/router/src/core/payment_methods.rs | 12 +- .../router/src/core/payment_methods/cards.rs | 338 +++++++++++------- .../surcharge_decision_configs.rs | 9 +- crates/router/src/core/payments/helpers.rs | 4 +- crates/router/src/routes/app.rs | 7 + crates/router/src/routes/payment_methods.rs | 122 +++++++ .../src/types/storage/payment_method.rs | 1 + 9 files changed, 362 insertions(+), 136 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index d63d2b085b6..448db8111fa 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -860,7 +860,7 @@ pub struct CustomerDefaultPaymentMethodResponse { pub struct CustomerPaymentMethod { /// Token for payment method in temporary card locker which gets refreshed often #[schema(example = "7ebf443f-a050-4067-84e5-e6f6d4800aef")] - pub payment_token: String, + pub payment_token: Option, /// The unique identifier of the customer. #[schema(example = "pm_iouuy468iyuowqs")] pub payment_method_id: String, diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 8298595bbc5..0428d8e4cef 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -219,10 +219,11 @@ impl From for CustomerPaymentMethodList } } +// Check this in review impl From for PaymentMethodData { fn from(item: api_types::CustomerPaymentMethod) -> Self { Self { - id: item.payment_token, + id: item.payment_token.unwrap_or("".to_string()), object: "payment_method", card: item.card.map(From::from), created: item.created, diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 4b4eb34b7e2..49e586aa2eb 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -224,11 +224,13 @@ pub async fn retrieve_payment_method_with_token( .unwrap_or_default() } - storage::PaymentTokenData::WalletToken(_) => storage::PaymentMethodDataWithId { - payment_method: None, - payment_method_data: None, - payment_method_id: None, - }, + storage::PaymentTokenData::WalletToken(_) | storage::PaymentTokenData::Null => { + storage::PaymentMethodDataWithId { + payment_method: None, + payment_method_data: None, + payment_method_id: None, + } + } }; Ok(token) } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index cd51face62d..5ffb2897d29 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3083,6 +3083,58 @@ fn filter_pm_card_network_based( } } +pub async fn list_customer_payment_method_util( + state: routes::AppState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + req: Option, + generic_id: String, + is_payment_associated: bool, +) -> errors::RouterResponse { + let db = state.store.as_ref(); + let limit = req.clone().and_then(|pml_req| pml_req.limit); + + let (customer_id, payment_intent) = if is_payment_associated { + let cloned_secret = req.and_then(|r| r.client_secret.as_ref().cloned()); + let payment_intent = helpers::verify_payment_intent_time_and_client_secret( + db, + &merchant_account, + cloned_secret, + ) + .await?; + + ( + payment_intent + .as_ref() + .and_then(|pi| pi.customer_id.clone()), + payment_intent, + ) + } else { + (Some(generic_id), None) + }; + + let resp = if let Some(cust) = customer_id { + Box::pin(list_customer_payment_method( + &state, + merchant_account, + key_store, + payment_intent, + cust.as_str(), + limit, + is_payment_associated, + )) + .await? + } else { + let response = api::CustomerPaymentMethodsListResponse { + customer_payment_methods: Vec::new(), + is_guest_customer: Some(true), + }; + services::ApplicationResponse::Json(response) + }; + + Ok(resp) +} + pub async fn do_list_customer_pm_fetch_customer_if_not_passed( state: routes::AppState, merchant_account: domain::MerchantAccount, @@ -3101,6 +3153,7 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( None, customer_id, limit, + true, )) .await } else { @@ -3125,6 +3178,7 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( payment_intent, &customer_id, limit, + true, )) .await } @@ -3146,17 +3200,19 @@ pub async fn list_customer_payment_method( payment_intent: Option, customer_id: &str, limit: Option, + is_payment_associated: bool, ) -> errors::RouterResponse { let db = &*state.store; - let off_session_payment_flag = payment_intent - .as_ref() - .map(|pi| { - matches!( - pi.setup_future_usage, - Some(common_enums::FutureUsage::OffSession) - ) - }) - .unwrap_or(false); + let off_session_payment_flag = is_payment_associated + && payment_intent + .as_ref() + .map(|pi| { + matches!( + pi.setup_future_usage, + Some(common_enums::FutureUsage::OffSession) + ) + }) + .unwrap_or(false); let customer = db .find_customer_by_customer_id_merchant_id( @@ -3170,16 +3226,20 @@ pub async fn list_customer_payment_method( let key = key_store.key.get_inner().peek(); - let is_requires_cvv = db - .find_config_by_key_unwrap_or( - format!("{}_requires_cvv", merchant_account.merchant_id).as_str(), - Some("true".to_string()), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch requires_cvv config")?; + let requires_cvv = if is_payment_associated { + let is_requires_cvv = db + .find_config_by_key_unwrap_or( + format!("{}_requires_cvv", merchant_account.merchant_id).as_str(), + Some("true".to_string()), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch requires_cvv config")?; - let requires_cvv = is_requires_cvv.config != "false"; + is_requires_cvv.config != "false" + } else { + false + }; let resp = db .find_payment_method_by_customer_id_merchant_id_status( @@ -3194,9 +3254,11 @@ pub async fn list_customer_payment_method( //let mca = query::find_mca_by_merchant_id(conn, &merchant_account.merchant_id)?; let mut customer_pms = Vec::new(); for pm in resp.into_iter() { - let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token"); - let payment_method = pm.payment_method.get_required_value("payment_method")?; + let mut bank_details = None; + + let parent_payment_method_token = + is_payment_associated.then(|| generate_id(consts::ID_LENGTH, "token")); let payment_method_retrieval_context = match payment_method { enums::PaymentMethod::Card => { @@ -3207,11 +3269,15 @@ pub async fn list_customer_payment_method( card_details, #[cfg(feature = "payouts")] bank_transfer_details: None, - hyperswitch_token_data: PaymentTokenData::permanent_card( - Some(pm.payment_method_id.clone()), - pm.locker_id.clone().or(Some(pm.payment_method_id.clone())), - pm.locker_id.clone().unwrap_or(pm.payment_method_id.clone()), - ), + hyperswitch_token_data: if is_payment_associated { + PaymentTokenData::permanent_card( + Some(pm.payment_method_id.clone()), + pm.locker_id.clone().or(Some(pm.payment_method_id.clone())), + pm.locker_id.clone().unwrap_or(pm.payment_method_id.clone()), + ) + } else { + PaymentTokenData::Null + }, } } else { continue; @@ -3220,23 +3286,38 @@ pub async fn list_customer_payment_method( enums::PaymentMethod::BankDebit => { // Retrieve the pm_auth connector details so that it can be tokenized - let bank_account_token_data = get_bank_account_connector_details(&pm, key) - .await - .unwrap_or_else(|err| { - logger::error!(error=?err); - None - }); - if let Some(data) = bank_account_token_data { - let token_data = PaymentTokenData::AuthBankDebit(data); + let bank_account_token_data = if is_payment_associated { + get_bank_account_connector_details(&pm, key) + .await + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }) + } else { + None + }; - PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: token_data, - } + // Retrieve the masked bank details to be sent as a response + bank_details = if payment_method == enums::PaymentMethod::BankDebit { + get_masked_bank_details(&pm, key) + .await + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }) } else { - continue; + None + }; + + PaymentMethodListContext { + card_details: None, + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: if let Some(data) = bank_account_token_data { + PaymentTokenData::AuthBankDebit(data) + } else { + PaymentTokenData::Null + }, } } @@ -3256,16 +3337,18 @@ pub async fn list_customer_payment_method( get_bank_from_hs_locker( state, &key_store, - &parent_payment_method_token, + parent_payment_method_token.as_ref(), &pm.customer_id, &pm.merchant_id, pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id), ) .await?, ), - hyperswitch_token_data: PaymentTokenData::temporary_generic( - parent_payment_method_token.clone(), - ), + hyperswitch_token_data: if let Some(token) = parent_payment_method_token.as_ref() { + PaymentTokenData::temporary_generic(token.clone()) + } else { + PaymentTokenData::Null + }, }, _ => PaymentMethodListContext { @@ -3279,24 +3362,13 @@ pub async fn list_customer_payment_method( }, }; - // Retrieve the masked bank details to be sent as a response - let bank_details = if payment_method == enums::PaymentMethod::BankDebit { - get_masked_bank_details(&pm, key) - .await - .unwrap_or_else(|err| { - logger::error!(error=?err); - None - }) - } else { - None - }; - let payment_method_billing = decrypt_generic_data::( pm.payment_method_billing_address, key, ) .await .attach_printable("unable to decrypt payment method billing address details")?; + let connector_mandate_details = pm .connector_mandate_details .clone() @@ -3313,9 +3385,10 @@ pub async fn list_customer_payment_method( connector_mandate_details, ) .await?; + // Need validation for enabled payment method ,querying MCA let pma = api::CustomerPaymentMethod { - payment_token: parent_payment_method_token.to_owned(), + payment_token: parent_payment_method_token.clone(), payment_method_id: pm.payment_method_id.clone(), customer_id: pm.customer_id, payment_method, @@ -3343,51 +3416,54 @@ pub async fn list_customer_payment_method( let intent_created = payment_intent.as_ref().map(|intent| intent.created_at); - let redis_conn = state - .store - .get_redis_conn() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get redis connection")?; - ParentPaymentMethodToken::create_key_for_token(( - &parent_payment_method_token, - pma.payment_method, - )) - .insert( - intent_created, - payment_method_retrieval_context.hyperswitch_token_data, - state, - ) - .await?; - - if let Some(metadata) = pma.metadata { - let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata - .parse_value("PaymentMethodMetadata") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Failed to deserialize metadata to PaymentmethodMetadata struct", - )?; + if let Some(token) = parent_payment_method_token.as_ref() { + ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method)) + .insert( + intent_created, + payment_method_retrieval_context.hyperswitch_token_data, + state, + ) + .await?; + } - for pm_metadata in pm_metadata_vec.payment_method_tokenization { - let key = format!( - "pm_token_{}_{}_{}", - parent_payment_method_token, pma.payment_method, pm_metadata.0 - ); - let current_datetime_utc = common_utils::date_time::now(); - let time_elapsed = current_datetime_utc - - payment_intent - .as_ref() - .map(|intent| intent.created_at) - .unwrap_or_else(|| current_datetime_utc); - redis_conn - .set_key_with_expiry( - &key, - pm_metadata.1, - consts::TOKEN_TTL - time_elapsed.whole_seconds(), - ) - .await - .change_context(errors::StorageError::KVError) + // Check this block in review + if let Some(token) = parent_payment_method_token { + if let Some(metadata) = pma.metadata { + let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata + .parse_value("PaymentMethodMetadata") .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add data in redis")?; + .attach_printable( + "Failed to deserialize metadata to PaymentmethodMetadata struct", + )?; + + for pm_metadata in pm_metadata_vec.payment_method_tokenization { + let key = format!( + "pm_token_{}_{}_{}", + token, pma.payment_method, pm_metadata.0 + ); + let current_datetime_utc = common_utils::date_time::now(); + let time_elapsed = current_datetime_utc + - payment_intent + .as_ref() + .map(|intent| intent.created_at) + .unwrap_or_else(|| current_datetime_utc); + + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + redis_conn + .set_key_with_expiry( + &key, + pm_metadata.1, + consts::TOKEN_TTL - time_elapsed.whole_seconds(), + ) + .await + .change_context(errors::StorageError::KVError) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add data in redis")?; + } } } } @@ -3396,22 +3472,26 @@ pub async fn list_customer_payment_method( customer_payment_methods: customer_pms, is_guest_customer: payment_intent.as_ref().map(|_| false), //to return this key only when the request is tied to a payment intent }; - let payment_attempt = payment_intent - .as_ref() - .async_map(|payment_intent| async { - state - .store - .find_payment_attempt_by_payment_id_merchant_id_attempt_id( - &payment_intent.payment_id, - &merchant_account.merchant_id, - &payment_intent.active_attempt.get_id(), - merchant_account.storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) - }) - .await - .transpose()?; + let payment_attempt = if is_payment_associated { + payment_intent + .as_ref() + .async_map(|payment_intent| async { + state + .store + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &payment_intent.payment_id, + &merchant_account.merchant_id, + &payment_intent.active_attempt.get_id(), + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) + }) + .await + .transpose()? + } else { + None + }; let profile_id = payment_intent .as_ref() @@ -3429,6 +3509,7 @@ pub async fn list_customer_payment_method( }) .await .transpose()?; + let business_profile = core_utils::validate_and_get_business_profile( db, profile_id.as_ref(), @@ -3454,6 +3535,7 @@ pub async fn list_customer_payment_method( Ok(services::ApplicationResponse::Json(response)) } + pub async fn get_mca_status( state: &routes::AppState, key_store: &domain::MerchantKeyStore, @@ -3798,7 +3880,7 @@ pub async fn update_last_used_at( pub async fn get_bank_from_hs_locker( state: &routes::AppState, key_store: &domain::MerchantKeyStore, - temp_token: &str, + temp_token: Option<&String>, customer_id: &str, merchant_id: &str, token_ref: &str, @@ -3821,16 +3903,18 @@ pub async fn get_bank_from_hs_locker( .change_context(errors::ApiErrorResponse::InternalServerError)?; match &pm_parsed { api::PayoutMethodData::Bank(bank) => { - vault::Vault::store_payout_method_data_in_locker( - state, - Some(temp_token.to_string()), - &pm_parsed, - Some(customer_id.to_owned()), - key_store, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error storing payout method data in temporary locker")?; + if let Some(token) = temp_token { + vault::Vault::store_payout_method_data_in_locker( + state, + Some(token.clone()), + &pm_parsed, + Some(customer_id.to_owned()), + key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error storing payout method data in temporary locker")?; + } Ok(bank.to_owned()) } api::PayoutMethodData::Card(_) => Err(errors::ApiErrorResponse::InvalidRequestData { diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 76881950e3b..6003f0629e1 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -26,6 +26,7 @@ use crate::{ storage::{self, payment_attempt::PaymentAttemptExt}, transformers::ForeignTryFrom, }, + utils::OptionExt, AppState, }; @@ -312,6 +313,12 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( .change_context(ConfigError::InputConstructionError)?; for customer_payment_method in customer_payment_method_list.iter_mut() { + let payment_token = customer_payment_method + .payment_token + .clone() + .get_required_value("payment_token") + .change_context(ConfigError::InputConstructionError)?; + backend_input.payment_method.payment_method = Some(customer_payment_method.payment_method); backend_input.payment_method.payment_method_type = customer_payment_method.payment_method_type; @@ -332,7 +339,7 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( payment_attempt, ( &mut surcharge_metadata, - types::SurchargeKey::Token(customer_payment_method.payment_token.clone()), + types::SurchargeKey::Token(payment_token), ), )?; customer_payment_method.surcharge_details = surcharge_details diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 6e3f6a9bf6e..df29e0554e8 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1854,7 +1854,8 @@ pub async fn retrieve_payment_method_from_db_with_token_data( storage::PaymentTokenData::Temporary(_) | storage::PaymentTokenData::TemporaryGeneric(_) | storage::PaymentTokenData::Permanent(_) - | storage::PaymentTokenData::AuthBankDebit(_) => Ok(None), + | storage::PaymentTokenData::AuthBankDebit(_) + | storage::PaymentTokenData::Null => Ok(None), } } @@ -4543,6 +4544,7 @@ pub async fn get_payment_method_details_from_payment_token( } storage::PaymentTokenData::WalletToken(_) => Ok(None), + storage::PaymentTokenData::Null => Ok(None), } } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 141e9f53af4..ef121da1d03 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -404,6 +404,9 @@ impl Payments { ) .service( web::resource("/{payment_id}/extended_card_info").route(web::get().to(retrieve_extended_card_info)), + ) + .service( + web::resource("/v2/{payment_id}/saved_payment_methods").route(web::get().to(list_customer_payment_method_for_payment)), ); } route @@ -702,6 +705,10 @@ impl Customers { .route(web::get().to(customers_retrieve)) .route(web::post().to(customers_update)) .route(web::delete().to(customers_delete)), + ) + .service( + web::resource("/v2/{customer_id}/saved_payment_methods") + .route(web::get().to(list_customer_payment_method_api_v2)), ); } diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index e9dacd9f84f..a7d13c1c709 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -163,6 +163,128 @@ pub async fn list_customer_payment_method_api( )) .await } + +/// List payment methods for a Customer v2 +/// +/// To filter and list the applicable payment methods for a particular Customer ID, is to be assocaited with a payment +#[utoipa::path( + get, + path = "/payments/v2/{payment_id}/saved_payment_methods", + params ( + ("client-secret" = String, Path, description = "A secret known only to your application and the authorization server"), + ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), + ("accepted_currency" = Vec, Path, description = "The three-letter ISO currency code"), + ("minimum_amount" = i64, Query, description = "The minimum amount accepted for processing by the particular payment method."), + ("maximum_amount" = i64, Query, description = "The maximum amount amount accepted for processing by the particular payment method."), + ("recurring_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for recurring payments"), + ("installment_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for installment payments"), + ), + responses( + (status = 200, description = "Payment Methods retrieved for customer tied to its respective client-secret passed in the param", body = CustomerPaymentMethodsListResponse), + (status = 400, description = "Invalid Data"), + (status = 404, description = "Payment Methods does not exist in records") + ), + tag = "Payment Methods", + operation_id = "List all Payment Methods for a Customer", + security(("publishable_key" = [])) +)] +#[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] +pub async fn list_customer_payment_method_for_payment( + state: web::Data, + payment_id: web::Path<(String,)>, + req: HttpRequest, + query_payload: web::Query, +) -> HttpResponse { + let flow = Flow::CustomerPaymentMethodsList; + let payload = query_payload.into_inner(); + let payment_id = payment_id.into_inner().0.clone(); + + let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) { + Ok((auth, _auth_flow)) => (auth, _auth_flow), + Err(e) => return api::log_and_return_error_response(e), + }; + + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, req, _| { + cards::list_customer_payment_method_util( + state, + auth.merchant_account, + auth.key_store, + Some(req), + payment_id.clone(), + true, + ) + }, + &*auth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +/// List payment methods for a Customer v2 +/// +/// To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context +#[utoipa::path( + get, + path = "/customers/v2/{customer_id}/saved_payment_methods", + params ( + ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), + ("accepted_currency" = Vec, Path, description = "The three-letter ISO currency code"), + ("minimum_amount" = i64, Query, description = "The minimum amount accepted for processing by the particular payment method."), + ("maximum_amount" = i64, Query, description = "The maximum amount amount accepted for processing by the particular payment method."), + ("recurring_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for recurring payments"), + ("installment_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for installment payments"), + ), + responses( + (status = 200, description = "Payment Methods retrieved", body = CustomerPaymentMethodsListResponse), + (status = 400, description = "Invalid Data"), + (status = 404, description = "Payment Methods does not exist in records") + ), + tag = "Payment Methods", + operation_id = "List all Payment Methods for a Customer", + security(("api_key" = [])) +)] +#[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] +pub async fn list_customer_payment_method_api_v2( + state: web::Data, + customer_id: web::Path<(String,)>, + req: HttpRequest, + query_payload: web::Query, +) -> HttpResponse { + let flow = Flow::CustomerPaymentMethodsList; + let payload = query_payload.into_inner(); + let customer_id = customer_id.into_inner().0.clone(); + + let ephemeral_or_api_auth = + match auth::is_ephemeral_auth(req.headers(), &*state.store, &customer_id).await { + Ok(auth) => auth, + Err(err) => return api::log_and_return_error_response(err), + }; + + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, req, _| { + cards::list_customer_payment_method_util( + state, + auth.merchant_account, + auth.key_store, + Some(req), + customer_id.clone(), + false, + ) + }, + &*ephemeral_or_api_auth, + api_locking::LockAction::NotApplicable, + )) + .await +} /// List payment methods for a Customer /// /// To filter and list the applicable payment methods for a particular Customer ID diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs index d9f96ef482c..5c1d606e42b 100644 --- a/crates/router/src/types/storage/payment_method.rs +++ b/crates/router/src/types/storage/payment_method.rs @@ -54,6 +54,7 @@ pub enum PaymentTokenData { PermanentCard(CardTokenData), AuthBankDebit(payment_methods::BankAccountTokenData), WalletToken(WalletTokenData), + Null, } impl PaymentTokenData { From cd892963789dcffb65c1211476404b302ca798ca Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 08:16:13 +0000 Subject: [PATCH 02/30] docs(openapi): re-generate OpenAPI specification --- openapi/openapi_spec.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 87f05f3b675..291940a2701 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -8328,7 +8328,6 @@ "CustomerPaymentMethod": { "type": "object", "required": [ - "payment_token", "payment_method_id", "customer_id", "payment_method", @@ -8341,7 +8340,8 @@ "payment_token": { "type": "string", "description": "Token for payment method in temporary card locker which gets refreshed often", - "example": "7ebf443f-a050-4067-84e5-e6f6d4800aef" + "example": "7ebf443f-a050-4067-84e5-e6f6d4800aef", + "nullable": true }, "payment_method_id": { "type": "string", From a580b353662b8beda849c5ed72c919fed31f6b98 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 5 Jun 2024 15:26:16 +0530 Subject: [PATCH 03/30] fix(payment_methods): Fixed clippy errors --- crates/router/src/core/payment_methods/cards.rs | 6 +++--- crates/router/src/routes/payment_methods.rs | 17 ++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index c811c5ad005..b66e1064bd0 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3322,7 +3322,7 @@ pub async fn list_customer_payment_method_util( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: Option, - generic_id: String, + customer_id: Option, is_payment_associated: bool, ) -> errors::RouterResponse { let db = state.store.as_ref(); @@ -3344,7 +3344,7 @@ pub async fn list_customer_payment_method_util( payment_intent, ) } else { - (Some(generic_id), None) + (customer_id, None) }; let resp = if let Some(cust) = customer_id { @@ -3353,7 +3353,7 @@ pub async fn list_customer_payment_method_util( merchant_account, key_store, payment_intent, - cust.as_str(), + &cust, limit, is_payment_associated, )) diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 9a9ac69d099..bdc24e9b30d 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -196,7 +196,7 @@ pub async fn list_customer_payment_method_for_payment( ) -> HttpResponse { let flow = Flow::CustomerPaymentMethodsList; let payload = query_payload.into_inner(); - let payment_id = payment_id.into_inner().0.clone(); + let _payment_id = payment_id.into_inner().0.clone(); let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) { Ok((auth, _auth_flow)) => (auth, _auth_flow), @@ -214,7 +214,7 @@ pub async fn list_customer_payment_method_for_payment( auth.merchant_account, auth.key_store, Some(req), - payment_id.clone(), + None, true, ) }, @@ -250,7 +250,7 @@ pub async fn list_customer_payment_method_for_payment( #[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] pub async fn list_customer_payment_method_api_v2( state: web::Data, - customer_id: web::Path<(String,)>, + customer_id: web::Path<(id_type::CustomerId,)>, req: HttpRequest, query_payload: web::Query, ) -> HttpResponse { @@ -258,11 +258,10 @@ pub async fn list_customer_payment_method_api_v2( let payload = query_payload.into_inner(); let customer_id = customer_id.into_inner().0.clone(); - let ephemeral_or_api_auth = - match auth::is_ephemeral_auth(req.headers(), &*state.store, &customer_id).await { - Ok(auth) => auth, - Err(err) => return api::log_and_return_error_response(err), - }; + let ephemeral_or_api_auth = match auth::is_ephemeral_auth(req.headers(), &customer_id) { + Ok(auth) => auth, + Err(err) => return api::log_and_return_error_response(err), + }; Box::pin(api::server_wrap( flow, @@ -275,7 +274,7 @@ pub async fn list_customer_payment_method_api_v2( auth.merchant_account, auth.key_store, Some(req), - customer_id.clone(), + Some(customer_id.clone()), false, ) }, From 4eae216b72c638d621ac12e8e1869238d92078d0 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 5 Jun 2024 15:30:07 +0530 Subject: [PATCH 04/30] fix(payment_methods): Fixed clippy errors --- crates/router/src/routes/payment_methods.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index bdc24e9b30d..b22ab60b713 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -165,7 +165,7 @@ pub async fn list_customer_payment_method_api( /// List payment methods for a Customer v2 /// -/// To filter and list the applicable payment methods for a particular Customer ID, is to be assocaited with a payment +/// To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment #[utoipa::path( get, path = "/payments/v2/{payment_id}/saved_payment_methods", From 51c7d126ecb68d0fbef421e7a72785c405db953b Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Mon, 10 Jun 2024 16:04:36 +0530 Subject: [PATCH 05/30] fix(payment_methods): Resolved comments --- .../compatibility/stripe/customers/types.rs | 4 +- crates/router/src/core/payment_methods.rs | 12 ++--- .../router/src/core/payment_methods/cards.rs | 50 +++++++++---------- crates/router/src/core/payments/helpers.rs | 4 +- .../src/types/storage/payment_method.rs | 3 +- 5 files changed, 34 insertions(+), 39 deletions(-) diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 7515c364809..9165b94085e 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -190,7 +190,7 @@ pub struct CustomerPaymentMethodListResponse { #[derive(Default, Serialize, PartialEq, Eq)] pub struct PaymentMethodData { - pub id: String, + pub id: Option, pub object: &'static str, pub card: Option, pub created: Option, @@ -223,7 +223,7 @@ impl From for CustomerPaymentMethodList impl From for PaymentMethodData { fn from(item: api_types::CustomerPaymentMethod) -> Self { Self { - id: item.payment_token.unwrap_or("".to_string()), + id: item.payment_token, object: "payment_method", card: item.card.map(From::from), created: item.created, diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index c628abc8943..b6d95248abc 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -223,13 +223,11 @@ pub async fn retrieve_payment_method_with_token( .unwrap_or_default() } - storage::PaymentTokenData::WalletToken(_) | storage::PaymentTokenData::Null => { - storage::PaymentMethodDataWithId { - payment_method: None, - payment_method_data: None, - payment_method_id: None, - } - } + storage::PaymentTokenData::WalletToken(_) => storage::PaymentMethodDataWithId { + payment_method: None, + payment_method_data: None, + payment_method_id: None, + }, }; Ok(token) } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index b66e1064bd0..df39b29ad2c 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3326,10 +3326,10 @@ pub async fn list_customer_payment_method_util( is_payment_associated: bool, ) -> errors::RouterResponse { let db = state.store.as_ref(); - let limit = req.clone().and_then(|pml_req| pml_req.limit); + let limit = req.as_ref().and_then(|pml_req| pml_req.limit); let (customer_id, payment_intent) = if is_payment_associated { - let cloned_secret = req.and_then(|r| r.client_secret.as_ref().cloned()); + let cloned_secret = req.and_then(|r| r.client_secret.clone()); let payment_intent = helpers::verify_payment_intent_time_and_client_secret( db, &merchant_account, @@ -3504,13 +3504,13 @@ pub async fn list_customer_payment_method( #[cfg(feature = "payouts")] bank_transfer_details: None, hyperswitch_token_data: if is_payment_associated { - PaymentTokenData::permanent_card( + Some(PaymentTokenData::permanent_card( Some(pm.payment_method_id.clone()), pm.locker_id.clone().or(Some(pm.payment_method_id.clone())), pm.locker_id.clone().unwrap_or(pm.payment_method_id.clone()), - ) + )) } else { - PaymentTokenData::Null + None }, } } else { @@ -3547,11 +3547,8 @@ pub async fn list_customer_payment_method( card_details: None, #[cfg(feature = "payouts")] bank_transfer_details: None, - hyperswitch_token_data: if let Some(data) = bank_account_token_data { - PaymentTokenData::AuthBankDebit(data) - } else { - PaymentTokenData::Null - }, + hyperswitch_token_data: bank_account_token_data + .map(PaymentTokenData::AuthBankDebit), } } @@ -3559,9 +3556,9 @@ pub async fn list_customer_payment_method( card_details: None, #[cfg(feature = "payouts")] bank_transfer_details: None, - hyperswitch_token_data: PaymentTokenData::wallet_token( + hyperswitch_token_data: Some(PaymentTokenData::wallet_token( pm.payment_method_id.clone(), - ), + )), }, #[cfg(feature = "payouts")] @@ -3578,21 +3575,19 @@ pub async fn list_customer_payment_method( ) .await?, ), - hyperswitch_token_data: if let Some(token) = parent_payment_method_token.as_ref() { - PaymentTokenData::temporary_generic(token.clone()) - } else { - PaymentTokenData::Null - }, + hyperswitch_token_data: parent_payment_method_token + .as_ref() + .map(|token| PaymentTokenData::temporary_generic(token.clone())), }, _ => PaymentMethodListContext { card_details: None, #[cfg(feature = "payouts")] bank_transfer_details: None, - hyperswitch_token_data: PaymentTokenData::temporary_generic(generate_id( + hyperswitch_token_data: Some(PaymentTokenData::temporary_generic(generate_id( consts::ID_LENGTH, "token", - )), + ))), }, }; @@ -3650,13 +3645,18 @@ pub async fn list_customer_payment_method( let intent_created = payment_intent.as_ref().map(|intent| intent.created_at); - if let Some(token) = parent_payment_method_token.as_ref() { + if is_payment_associated { + let token = parent_payment_method_token + .as_ref() + .get_required_value("parent_payment_method_token")?; + let hyperswitch_token_data = payment_method_retrieval_context + .hyperswitch_token_data + .as_ref() + .cloned() + .get_required_value("PaymentTokenData")?; + ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method)) - .insert( - intent_created, - payment_method_retrieval_context.hyperswitch_token_data, - state, - ) + .insert(intent_created, hyperswitch_token_data, state) .await?; } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 7bfb4cfa1a5..d11d0ef3c84 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1865,8 +1865,7 @@ pub async fn retrieve_payment_method_from_db_with_token_data( storage::PaymentTokenData::Temporary(_) | storage::PaymentTokenData::TemporaryGeneric(_) | storage::PaymentTokenData::Permanent(_) - | storage::PaymentTokenData::AuthBankDebit(_) - | storage::PaymentTokenData::Null => Ok(None), + | storage::PaymentTokenData::AuthBankDebit(_) => Ok(None), } } @@ -4555,7 +4554,6 @@ pub async fn get_payment_method_details_from_payment_token( } storage::PaymentTokenData::WalletToken(_) => Ok(None), - storage::PaymentTokenData::Null => Ok(None), } } diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs index 5c1d606e42b..d8811edf388 100644 --- a/crates/router/src/types/storage/payment_method.rs +++ b/crates/router/src/types/storage/payment_method.rs @@ -54,7 +54,6 @@ pub enum PaymentTokenData { PermanentCard(CardTokenData), AuthBankDebit(payment_methods::BankAccountTokenData), WalletToken(WalletTokenData), - Null, } impl PaymentTokenData { @@ -82,7 +81,7 @@ impl PaymentTokenData { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentMethodListContext { pub card_details: Option, - pub hyperswitch_token_data: PaymentTokenData, + pub hyperswitch_token_data: Option, #[cfg(feature = "payouts")] pub bank_transfer_details: Option, } From bc62440f26c1178a6b465dde05ae4fdcee052d38 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 20 Jun 2024 16:39:51 +0530 Subject: [PATCH 06/30] fix(payment_methods): Fixed errors --- crates/router/src/core/payment_methods/cards.rs | 1 + crates/router/src/routes/payment_methods.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index d124cf72989..e9a58201dc5 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3366,6 +3366,7 @@ pub async fn list_customer_payment_method_util( let payment_intent = helpers::verify_payment_intent_time_and_client_secret( db, &merchant_account, + &key_store, cloned_secret, ) .await?; diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 1ad3d94e423..6bf225fcf6b 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -259,7 +259,7 @@ pub async fn list_customer_payment_method_api_v2( let payload = query_payload.into_inner(); let customer_id = customer_id.into_inner().0.clone(); - let ephemeral_or_api_auth = match auth::is_ephemeral_auth(req.headers(), &customer_id) { + let ephemeral_or_api_auth = match auth::is_ephemeral_auth(req.headers()) { Ok(auth) => auth, Err(err) => return api::log_and_return_error_response(err), }; From ddd9fd61f1ce0a416701289d4b6496dd269b785b Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 26 Jun 2024 16:48:46 +0530 Subject: [PATCH 07/30] fix(payment_methods): Resolved comments --- crates/router/src/core/payment_methods/cards.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index e9a58201dc5..2438ce981fe 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3708,10 +3708,7 @@ pub async fn list_customer_payment_method( ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method)) .insert(intent_created, hyperswitch_token_data, state) .await?; - } - // Check this block in review - if let Some(token) = parent_payment_method_token { if let Some(metadata) = pma.metadata { let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata .parse_value("PaymentMethodMetadata") From b2c027488ce4a2338d3b48486d9cce5911fba449 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Mon, 1 Jul 2024 20:01:52 +0530 Subject: [PATCH 08/30] refactor(payment_methods): refactored for v2 flag --- crates/api_models/Cargo.toml | 1 + crates/api_models/src/payment_methods.rs | 97 +++++ crates/router/Cargo.toml | 1 + crates/router/src/compatibility/stripe.rs | 1 + crates/router/src/compatibility/stripe/app.rs | 1 + .../src/compatibility/stripe/customers.rs | 1 + .../compatibility/stripe/customers/types.rs | 12 +- .../router/src/core/payment_methods/cards.rs | 334 +++++++++++++++++- .../surcharge_decision_configs.rs | 24 +- crates/router/src/lib.rs | 2 +- crates/router/src/routes/app.rs | 36 +- crates/router/src/routes/payment_methods.rs | 12 +- .../router/src/types/api/payment_methods.rs | 13 + .../src/types/storage/payment_method.rs | 10 + 14 files changed, 525 insertions(+), 20 deletions(-) diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index 0bd0b01a278..78a14f93dc9 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -21,6 +21,7 @@ frm = [] olap = [] openapi = ["common_enums/openapi", "olap", "backwards_compatibility", "business_profile_routing", "connector_choice_mca_id", "recon", "dummy_connector", "olap"] recon = [] +v2 = [] [dependencies] actix-web = { version = "4.5.1", optional = true } diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index fcca1f60891..919656c3462 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -826,6 +826,16 @@ impl serde::Serialize for PaymentMethodList { } } +#[cfg(not(feature = "v2"))] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct CustomerPaymentMethodsListResponse { + /// List of payment methods for customer + pub customer_payment_methods: Vec, + /// Returns whether a customer id is not tied to a payment intent (only when the request is made against a client secret) + pub is_guest_customer: Option, +} + +#[cfg(feature = "v2")] #[derive(Debug, serde::Serialize, ToSchema)] pub struct CustomerPaymentMethodsListResponse { /// List of payment methods for customer @@ -860,6 +870,7 @@ pub struct CustomerDefaultPaymentMethodResponse { pub payment_method_type: Option, } +#[cfg(feature = "v2")] #[derive(Debug, Clone, serde::Serialize, ToSchema)] pub struct CustomerPaymentMethod { /// Token for payment method in temporary card locker which gets refreshed often @@ -901,6 +912,92 @@ pub struct CustomerPaymentMethod { #[schema(value_type = Option>,example = json!(["redirect_to_url"]))] pub payment_experience: Option>, + /// PaymentMethod Data from locker + pub payment_method_data: Option, + + /// Masked bank details from PM auth services + #[schema(example = json!({"mask": "0000"}))] + pub bank: 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. + #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] + pub metadata: Option, + + /// A timestamp (ISO 8601 code) that determines when the customer was created + #[schema(value_type = Option,example = "2023-01-18T11:04:09.922Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub created: Option, + + /// Surcharge details for this saved card + pub surcharge_details: Option, + + /// Whether this payment method requires CVV to be collected + #[schema(example = true)] + pub requires_cvv: bool, + + /// A timestamp (ISO 8601 code) that determines when the payment method was last used + #[schema(value_type = Option,example = "2024-02-24T11:04:09.922Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub last_used_at: Option, + /// Indicates if the payment method has been set to default or not + #[schema(example = true)] + pub default_payment_method_set: bool, + + /// The billing details of the payment method + #[schema(value_type = Option
)] + pub billing: Option, +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Serialize, ToSchema)] +pub enum PaymentMethodListData { + Card(CardDetailFromLocker), + #[cfg(feature = "payouts")] + Bank(payouts::Bank), +} + +#[cfg(not(feature = "v2"))] +#[derive(Debug, Clone, serde::Serialize, ToSchema)] +pub struct CustomerPaymentMethod { + /// Token for payment method in temporary card locker which gets refreshed often + #[schema(example = "7ebf443f-a050-4067-84e5-e6f6d4800aef")] + pub payment_token: String, + /// The unique identifier of the customer. + #[schema(example = "pm_iouuy468iyuowqs")] + pub payment_method_id: String, + + /// The unique identifier of the customer. + #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] + pub customer_id: id_type::CustomerId, + + /// The type of payment method use for the payment. + #[schema(value_type = PaymentMethod,example = "card")] + pub payment_method: api_enums::PaymentMethod, + + /// This is a sub-category of payment method. + #[schema(value_type = Option,example = "credit_card")] + pub payment_method_type: Option, + + /// The name of the bank/ provider issuing the payment method to the end user + #[schema(example = "Citibank")] + pub payment_method_issuer: Option, + + /// A standard code representing the issuer of payment method + #[schema(value_type = Option,example = "jp_applepay")] + pub payment_method_issuer_code: Option, + + /// Indicates whether the payment method is eligible for recurring payments + #[schema(example = true)] + pub recurring_enabled: bool, + + /// Indicates whether the payment method is eligible for installment payments + #[schema(example = true)] + pub installment_payment_enabled: bool, + + /// Type of payment experience enabled with the connector + #[schema(value_type = Option>,example = json!(["redirect_to_url"]))] + pub payment_experience: Option>, + /// Card details from card locker #[schema(example = json!({"last4": "1142","exp_month": "03","exp_year": "2030"}))] pub card: Option, diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 871eed86893..7e06d0fa9ea 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -30,6 +30,7 @@ payouts = ["api_models/payouts", "common_enums/payouts", "hyperswitch_domain_mod payout_retry = ["payouts"] recon = ["email", "api_models/recon"] retry = [] +v2 = ["api_models/v2"] [dependencies] actix-cors = "0.6.5" diff --git a/crates/router/src/compatibility/stripe.rs b/crates/router/src/compatibility/stripe.rs index 5d3fd5b6cd1..3872adbf033 100644 --- a/crates/router/src/compatibility/stripe.rs +++ b/crates/router/src/compatibility/stripe.rs @@ -10,6 +10,7 @@ pub mod errors; use crate::routes; pub struct StripeApis; +#[cfg(not(feature = "v2"))] impl StripeApis { pub fn server(state: routes::AppState) -> Scope { let max_depth = 10; diff --git a/crates/router/src/compatibility/stripe/app.rs b/crates/router/src/compatibility/stripe/app.rs index 5b4be4dde0c..a863195e995 100644 --- a/crates/router/src/compatibility/stripe/app.rs +++ b/crates/router/src/compatibility/stripe/app.rs @@ -76,6 +76,7 @@ impl Refunds { pub struct Customers; +#[cfg(not(feature = "v2"))] impl Customers { pub fn server(config: routes::AppState) -> Scope { web::scope("/customers") diff --git a/crates/router/src/compatibility/stripe/customers.rs b/crates/router/src/compatibility/stripe/customers.rs index 264f205c907..5c2cba802da 100644 --- a/crates/router/src/compatibility/stripe/customers.rs +++ b/crates/router/src/compatibility/stripe/customers.rs @@ -163,6 +163,7 @@ pub async fn customer_delete( )) .await } +#[cfg(not(feature = "v2"))] #[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] pub async fn list_customer_payment_method_api( state: web::Data, diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 9165b94085e..6c09d09edf2 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -222,10 +222,20 @@ impl From for CustomerPaymentMethodList // Check this in review impl From for PaymentMethodData { fn from(item: api_types::CustomerPaymentMethod) -> Self { + #[cfg(not(feature = "v2"))] + let card = item.card.map(From::from); + #[cfg(feature = "v2")] + let card = match item.payment_method_data { + Some(api_types::PaymentMethodListData::Card(card)) => Some(CardDetails::from(card)), + _ => None + }; Self { + #[cfg(not(feature = "v2"))] + id: Some(item.payment_token), + #[cfg(feature = "v2")] id: item.payment_token, object: "payment_method", - card: item.card.map(From::from), + card, created: item.created, } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 2438ce981fe..a00c00a806b 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3350,6 +3350,7 @@ async fn filter_payment_mandate_based( Ok(recurring_filter) } +#[cfg(feature = "v2")] pub async fn list_customer_payment_method_util( state: routes::SessionState, merchant_account: domain::MerchantAccount, @@ -3403,6 +3404,7 @@ pub async fn list_customer_payment_method_util( Ok(resp) } +#[cfg(not(feature = "v2"))] pub async fn do_list_customer_pm_fetch_customer_if_not_passed( state: routes::SessionState, merchant_account: domain::MerchantAccount, @@ -3436,7 +3438,6 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( None, customer_id, limit, - true, )) .await } else { @@ -3462,7 +3463,6 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( payment_intent, &customer_id, limit, - true, )) .await } @@ -3477,6 +3477,325 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( } } +#[cfg(not(feature = "v2"))] +pub async fn list_customer_payment_method( + state: &routes::SessionState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + payment_intent: Option, + customer_id: &id_type::CustomerId, + limit: Option, +) -> errors::RouterResponse { + let db = &*state.store; + let off_session_payment_flag = payment_intent + .as_ref() + .map(|pi| { + matches!( + pi.setup_future_usage, + Some(common_enums::FutureUsage::OffSession) + ) + }) + .unwrap_or(false); + + let customer = db + .find_customer_by_customer_id_merchant_id( + customer_id, + &merchant_account.merchant_id, + &key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; + + let key = key_store.key.get_inner().peek(); + + let is_requires_cvv = db + .find_config_by_key_unwrap_or( + format!("{}_requires_cvv", merchant_account.merchant_id).as_str(), + Some("true".to_string()), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch requires_cvv config")?; + + let requires_cvv = is_requires_cvv.config != "false"; + + let resp = db + .find_payment_method_by_customer_id_merchant_id_status( + customer_id, + &merchant_account.merchant_id, + common_enums::PaymentMethodStatus::Active, + limit, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + //let mca = query::find_mca_by_merchant_id(conn, &merchant_account.merchant_id)?; + let mut customer_pms = Vec::new(); + for pm in resp.into_iter() { + let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token"); + + let payment_method = pm.payment_method.get_required_value("payment_method")?; + + let payment_method_retrieval_context = match payment_method { + enums::PaymentMethod::Card => { + let card_details = get_card_details_with_locker_fallback(&pm, key, state).await?; + + if card_details.is_some() { + PaymentMethodListContext { + card_details, + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: PaymentTokenData::permanent_card( + Some(pm.payment_method_id.clone()), + pm.locker_id.clone().or(Some(pm.payment_method_id.clone())), + pm.locker_id.clone().unwrap_or(pm.payment_method_id.clone()), + ), + } + } else { + continue; + } + } + + enums::PaymentMethod::BankDebit => { + // Retrieve the pm_auth connector details so that it can be tokenized + let bank_account_token_data = get_bank_account_connector_details(&pm, key) + .await + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }); + if let Some(data) = bank_account_token_data { + let token_data = PaymentTokenData::AuthBankDebit(data); + + PaymentMethodListContext { + card_details: None, + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: token_data, + } + } else { + continue; + } + } + + enums::PaymentMethod::Wallet => PaymentMethodListContext { + card_details: None, + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: PaymentTokenData::wallet_token( + pm.payment_method_id.clone(), + ), + }, + + #[cfg(feature = "payouts")] + enums::PaymentMethod::BankTransfer => PaymentMethodListContext { + card_details: None, + bank_transfer_details: Some( + get_bank_from_hs_locker( + state, + &key_store, + Some(parent_payment_method_token.clone()), + &pm.customer_id, + &pm.merchant_id, + pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id), + ) + .await?, + ), + hyperswitch_token_data: PaymentTokenData::temporary_generic( + parent_payment_method_token.clone(), + ), + }, + + _ => PaymentMethodListContext { + card_details: None, + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: PaymentTokenData::temporary_generic(generate_id( + consts::ID_LENGTH, + "token", + )), + }, + }; + + // Retrieve the masked bank details to be sent as a response + let bank_details = if payment_method == enums::PaymentMethod::BankDebit { + get_masked_bank_details(&pm, key) + .await + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }) + } else { + None + }; + + let payment_method_billing = decrypt_generic_data::( + pm.payment_method_billing_address, + key, + ) + .await + .attach_printable("unable to decrypt payment method billing address details")?; + let connector_mandate_details = pm + .connector_mandate_details + .clone() + .map(|val| { + val.parse_value::("PaymentsMandateReference") + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize to Payment Mandate Reference ")?; + let mca_enabled = get_mca_status( + state, + &key_store, + &merchant_account.merchant_id, + connector_mandate_details, + ) + .await?; + // Need validation for enabled payment method ,querying MCA + let pma = api::CustomerPaymentMethod { + payment_token: parent_payment_method_token.to_owned(), + payment_method_id: pm.payment_method_id.clone(), + customer_id: pm.customer_id, + payment_method, + payment_method_type: pm.payment_method_type, + payment_method_issuer: pm.payment_method_issuer, + card: payment_method_retrieval_context.card_details, + metadata: pm.metadata, + payment_method_issuer_code: pm.payment_method_issuer_code, + recurring_enabled: mca_enabled, + installment_payment_enabled: false, + payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), + created: Some(pm.created_at), + #[cfg(feature = "payouts")] + bank_transfer: payment_method_retrieval_context.bank_transfer_details, + bank: bank_details, + surcharge_details: None, + requires_cvv: requires_cvv + && !(off_session_payment_flag && pm.connector_mandate_details.is_some()), + last_used_at: Some(pm.last_used_at), + default_payment_method_set: customer.default_payment_method_id.is_some() + && customer.default_payment_method_id == Some(pm.payment_method_id), + billing: payment_method_billing, + }; + customer_pms.push(pma.to_owned()); + + let intent_created = payment_intent.as_ref().map(|intent| intent.created_at); + + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + ParentPaymentMethodToken::create_key_for_token(( + &parent_payment_method_token, + pma.payment_method, + )) + .insert( + intent_created, + payment_method_retrieval_context.hyperswitch_token_data, + state, + ) + .await?; + + if let Some(metadata) = pma.metadata { + let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata + .parse_value("PaymentMethodMetadata") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Failed to deserialize metadata to PaymentmethodMetadata struct", + )?; + + for pm_metadata in pm_metadata_vec.payment_method_tokenization { + let key = format!( + "pm_token_{}_{}_{}", + parent_payment_method_token, pma.payment_method, pm_metadata.0 + ); + let current_datetime_utc = common_utils::date_time::now(); + let time_elapsed = current_datetime_utc + - payment_intent + .as_ref() + .map(|intent| intent.created_at) + .unwrap_or_else(|| current_datetime_utc); + redis_conn + .set_key_with_expiry( + &key, + pm_metadata.1, + consts::TOKEN_TTL - time_elapsed.whole_seconds(), + ) + .await + .change_context(errors::StorageError::KVError) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add data in redis")?; + } + } + } + + let mut response = api::CustomerPaymentMethodsListResponse { + customer_payment_methods: customer_pms, + is_guest_customer: payment_intent.as_ref().map(|_| false), //to return this key only when the request is tied to a payment intent + }; + let payment_attempt = payment_intent + .as_ref() + .async_map(|payment_intent| async { + state + .store + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &payment_intent.payment_id, + &merchant_account.merchant_id, + &payment_intent.active_attempt.get_id(), + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) + }) + .await + .transpose()?; + + let profile_id = payment_intent + .as_ref() + .async_map(|payment_intent| async { + crate::core::utils::get_profile_id_from_business_details( + payment_intent.business_country, + payment_intent.business_label.as_ref(), + &merchant_account, + payment_intent.profile_id.as_ref(), + db, + false, + ) + .await + .attach_printable("Could not find profile id from business details") + }) + .await + .transpose()?; + let business_profile = core_utils::validate_and_get_business_profile( + db, + profile_id.as_ref(), + &merchant_account.merchant_id, + ) + .await?; + + if let Some((payment_attempt, payment_intent, business_profile)) = payment_attempt + .zip(payment_intent) + .zip(business_profile) + .map(|((pa, pi), bp)| (pa, pi, bp)) + { + call_surcharge_decision_management_for_saved_card( + state, + &merchant_account, + &key_store, + &business_profile, + &payment_attempt, + payment_intent, + &mut response, + ) + .await?; + } + + Ok(services::ApplicationResponse::Json(response)) +} + +#[cfg(feature = "v2")] pub async fn list_customer_payment_method( state: &routes::SessionState, merchant_account: domain::MerchantAccount, @@ -3665,6 +3984,13 @@ pub async fn list_customer_payment_method( ) .await?; + let pmd = if let Some(card) = payment_method_retrieval_context.card_details { + Some(api::PaymentMethodListData::Card(card)) + } else if let Some(bank) = payment_method_retrieval_context.bank_transfer_details { + Some(api::PaymentMethodListData::Bank(bank)) + } else { + None + }; // Need validation for enabled payment method ,querying MCA let pma = api::CustomerPaymentMethod { payment_token: parent_payment_method_token.clone(), @@ -3673,15 +3999,13 @@ pub async fn list_customer_payment_method( payment_method, payment_method_type: pm.payment_method_type, payment_method_issuer: pm.payment_method_issuer, - card: payment_method_retrieval_context.card_details, + payment_method_data: pmd, metadata: pm.metadata, payment_method_issuer_code: pm.payment_method_issuer_code, recurring_enabled: mca_enabled, installment_payment_enabled: false, payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), created: Some(pm.created_at), - #[cfg(feature = "payouts")] - bank_transfer: payment_method_retrieval_context.bank_transfer_details, bank: bank_details, surcharge_details: None, requires_cvv: requires_cvv diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 34e3bde4347..7393c5e64e5 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -296,6 +296,10 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( .change_context(ConfigError::InputConstructionError)?; for customer_payment_method in customer_payment_method_list.iter_mut() { + #[cfg(not(feature = "v2"))] + let payment_token = customer_payment_method.payment_token.clone(); + + #[cfg(feature = "v2")] let payment_token = customer_payment_method .payment_token .clone() @@ -305,7 +309,9 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( backend_input.payment_method.payment_method = Some(customer_payment_method.payment_method); backend_input.payment_method.payment_method_type = customer_payment_method.payment_method_type; - backend_input.payment_method.card_network = customer_payment_method + + #[cfg(not(feature = "v2"))] + let card_network = customer_payment_method .card .as_ref() .and_then(|card| card.scheme.as_ref()) @@ -316,6 +322,22 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( .change_context(ConfigError::DslExecutionError) }) .transpose()?; + #[cfg(feature = "v2")] + let card_network = match &customer_payment_method.payment_method_data { + Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => card + .scheme.as_ref() + .map(|scheme| { + scheme + .clone() + .parse_enum("CardNetwork") + .change_context(ConfigError::DslExecutionError) + }) + .transpose()?, + _ => None + }; + + backend_input.payment_method.card_network = card_network; + let surcharge_details = surcharge_source .generate_surcharge_details_and_populate_surcharge_metadata( &backend_input, diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index e3c01987cce..10c41efb306 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -158,7 +158,7 @@ pub fn mk_app( server_app = server_app.service(routes::Payouts::server(state.clone())); } - #[cfg(feature = "stripe")] + #[cfg(all(feature = "stripe", not(feature="v2")))] { server_app = server_app.service(routes::StripeApis::server(state.clone())); } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index fbe32db6e35..29a162a47af 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -446,7 +446,10 @@ pub struct Payments; #[cfg(any(feature = "olap", feature = "oltp"))] impl Payments { pub fn server(state: AppState) -> Scope { + #[cfg(not(feature="v2"))] let mut route = web::scope("/payments").app_data(web::Data::new(state)); + #[cfg(feature="v2")] + let mut route = web::scope("v2/payments").app_data(web::Data::new(state)); #[cfg(feature = "olap")] { @@ -529,9 +532,11 @@ impl Payments { .service( web::resource("/{payment_id}/extended_card_info").route(web::get().to(retrieve_extended_card_info)), ) - .service( - web::resource("/v2/{payment_id}/saved_payment_methods").route(web::get().to(list_customer_payment_method_for_payment)), - ); + } + #[cfg(all(feature = "oltp", feature="v2"))] { + route = route.service( + web::resource("/{payment_id}/saved_payment_methods").route(web::get().to(list_customer_payment_method_for_payment)), + ) } route } @@ -793,10 +798,10 @@ impl Routing { pub struct Customers; -#[cfg(any(feature = "olap", feature = "oltp"))] +#[cfg(all(any(feature = "olap", feature = "oltp"), not(feature = "v2")))] impl Customers { pub fn server(state: AppState) -> Scope { - let mut route = web::scope("/customers").app_data(web::Data::new(state)); + let mut route = web::scope("customers").app_data(web::Data::new(state)); #[cfg(feature = "olap")] { @@ -830,10 +835,23 @@ impl Customers { .route(web::post().to(customers_update)) .route(web::delete().to(customers_delete)), ) - .service( - web::resource("/v2/{customer_id}/saved_payment_methods") - .route(web::get().to(list_customer_payment_method_api_v2)), - ); + } + + route + } +} + +#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v2"))] +impl Customers { + pub fn server(state: AppState) -> Scope { + let mut route = web::scope("v2/customers").app_data(web::Data::new(state)); + + #[cfg(feature = "oltp")] + { + route = route.service( + web::resource("/{customer_id}/saved_payment_methods") + .route(web::get().to(list_customer_payment_method_api)), + ); } route diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 6bf225fcf6b..0ce6b14008d 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -105,6 +105,8 @@ pub async fn list_payment_method_api( )) .await } + +#[cfg(not(feature = "v2"))] /// List payment methods for a Customer /// /// To filter and list the applicable payment methods for a particular Customer ID @@ -164,12 +166,13 @@ pub async fn list_customer_payment_method_api( .await } +#[cfg(feature = "v2")] /// List payment methods for a Customer v2 /// /// To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment #[utoipa::path( get, - path = "/payments/v2/{payment_id}/saved_payment_methods", + path = "v2/payments/{payment_id}/saved_payment_methods", params ( ("client-secret" = String, Path, description = "A secret known only to your application and the authorization server"), ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), @@ -225,12 +228,13 @@ pub async fn list_customer_payment_method_for_payment( .await } +#[cfg(feature = "v2")] /// List payment methods for a Customer v2 /// /// To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context #[utoipa::path( get, - path = "/customers/v2/{customer_id}/saved_payment_methods", + path = "v2/customers/{customer_id}/saved_payment_methods", params ( ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), ("accepted_currency" = Vec, Path, description = "The three-letter ISO currency code"), @@ -249,7 +253,7 @@ pub async fn list_customer_payment_method_for_payment( security(("api_key" = [])) )] #[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] -pub async fn list_customer_payment_method_api_v2( +pub async fn list_customer_payment_method_api( state: web::Data, customer_id: web::Path<(id_type::CustomerId,)>, req: HttpRequest, @@ -284,6 +288,8 @@ pub async fn list_customer_payment_method_api_v2( )) .await } + +#[cfg(not(feature = "v2"))] /// List payment methods for a Customer /// /// To filter and list the applicable payment methods for a particular Customer ID diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 6bf100e4d67..091bd802754 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -1,3 +1,16 @@ +#[cfg(feature = "v2")] +pub use api_models::payment_methods::{ + CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, + CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, + GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, + PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, + PaymentMethodList, PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, + PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, + TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, + TokenizedWalletValue2, +}; + +#[cfg(not(feature = "v2"))] pub use api_models::payment_methods::{ CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs index d8811edf388..4d8ed7914d4 100644 --- a/crates/router/src/types/storage/payment_method.rs +++ b/crates/router/src/types/storage/payment_method.rs @@ -78,6 +78,16 @@ impl PaymentTokenData { } } +#[cfg(not(feature = "v2"))] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentMethodListContext { + pub card_details: Option, + pub hyperswitch_token_data: PaymentTokenData, + #[cfg(feature = "payouts")] + pub bank_transfer_details: Option, +} + +#[cfg(feature = "v2")] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentMethodListContext { pub card_details: Option, From 2ac2fa93ac069ec85f797ab5cad035d70c64075d Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:32:39 +0000 Subject: [PATCH 09/30] chore: run formatter --- .../compatibility/stripe/customers/types.rs | 2 +- .../surcharge_decision_configs.rs | 19 ++++++++++--------- crates/router/src/lib.rs | 2 +- crates/router/src/routes/app.rs | 10 ++++++---- .../router/src/types/api/payment_methods.rs | 1 - 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 6c09d09edf2..fbe6b56a885 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -227,7 +227,7 @@ impl From for PaymentMethodData { #[cfg(feature = "v2")] let card = match item.payment_method_data { Some(api_types::PaymentMethodListData::Card(card)) => Some(CardDetails::from(card)), - _ => None + _ => None, }; Self { #[cfg(not(feature = "v2"))] diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 7393c5e64e5..a9ed7f6cb77 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -325,15 +325,16 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( #[cfg(feature = "v2")] let card_network = match &customer_payment_method.payment_method_data { Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => card - .scheme.as_ref() - .map(|scheme| { - scheme - .clone() - .parse_enum("CardNetwork") - .change_context(ConfigError::DslExecutionError) - }) - .transpose()?, - _ => None + .scheme + .as_ref() + .map(|scheme| { + scheme + .clone() + .parse_enum("CardNetwork") + .change_context(ConfigError::DslExecutionError) + }) + .transpose()?, + _ => None, }; backend_input.payment_method.card_network = card_network; diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 10c41efb306..46002efa8c0 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -158,7 +158,7 @@ pub fn mk_app( server_app = server_app.service(routes::Payouts::server(state.clone())); } - #[cfg(all(feature = "stripe", not(feature="v2")))] + #[cfg(all(feature = "stripe", not(feature = "v2")))] { server_app = server_app.service(routes::StripeApis::server(state.clone())); } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 29a162a47af..17ea4261185 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -446,9 +446,9 @@ pub struct Payments; #[cfg(any(feature = "olap", feature = "oltp"))] impl Payments { pub fn server(state: AppState) -> Scope { - #[cfg(not(feature="v2"))] + #[cfg(not(feature = "v2"))] let mut route = web::scope("/payments").app_data(web::Data::new(state)); - #[cfg(feature="v2")] + #[cfg(feature = "v2")] let mut route = web::scope("v2/payments").app_data(web::Data::new(state)); #[cfg(feature = "olap")] @@ -533,9 +533,11 @@ impl Payments { web::resource("/{payment_id}/extended_card_info").route(web::get().to(retrieve_extended_card_info)), ) } - #[cfg(all(feature = "oltp", feature="v2"))] { + #[cfg(all(feature = "oltp", feature = "v2"))] + { route = route.service( - web::resource("/{payment_id}/saved_payment_methods").route(web::get().to(list_customer_payment_method_for_payment)), + web::resource("/{payment_id}/saved_payment_methods") + .route(web::get().to(list_customer_payment_method_for_payment)), ) } route diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 091bd802754..97af9e3f870 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -9,7 +9,6 @@ pub use api_models::payment_methods::{ TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, }; - #[cfg(not(feature = "v2"))] pub use api_models::payment_methods::{ CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, From d0e505a78cb2e4c98ab2e0674f0408e158f18e4f Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:35:04 +0000 Subject: [PATCH 10/30] docs(openapi): re-generate OpenAPI specification --- api-reference/openapi_spec.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 8f771f79895..dc0bd4ef074 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -8356,6 +8356,7 @@ "CustomerPaymentMethod": { "type": "object", "required": [ + "payment_token", "payment_method_id", "customer_id", "payment_method", @@ -8368,8 +8369,7 @@ "payment_token": { "type": "string", "description": "Token for payment method in temporary card locker which gets refreshed often", - "example": "7ebf443f-a050-4067-84e5-e6f6d4800aef", - "nullable": true + "example": "7ebf443f-a050-4067-84e5-e6f6d4800aef" }, "payment_method_id": { "type": "string", From d19ca370836559f0a9705ffe083f10dff01852c3 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 2 Jul 2024 14:53:25 +0530 Subject: [PATCH 11/30] fix(payment_methods): Fixed errors --- crates/router/src/compatibility/stripe.rs | 3 +- crates/router/src/compatibility/stripe/app.rs | 6 +- .../src/compatibility/stripe/customers.rs | 10 ++ .../router/src/core/payment_methods/cards.rs | 97 +++++++------------ .../surcharge_decision_configs.rs | 3 + crates/router/src/routes/app.rs | 4 +- 6 files changed, 55 insertions(+), 68 deletions(-) diff --git a/crates/router/src/compatibility/stripe.rs b/crates/router/src/compatibility/stripe.rs index 3872adbf033..cbc38128dac 100644 --- a/crates/router/src/compatibility/stripe.rs +++ b/crates/router/src/compatibility/stripe.rs @@ -4,9 +4,10 @@ pub mod payment_intents; pub mod refunds; pub mod setup_intents; pub mod webhooks; +#[cfg(not(feature = "v2"))] use actix_web::{web, Scope}; pub mod errors; - +#[cfg(not(feature = "v2"))] use crate::routes; pub struct StripeApis; diff --git a/crates/router/src/compatibility/stripe/app.rs b/crates/router/src/compatibility/stripe/app.rs index a863195e995..19d1f5aea5e 100644 --- a/crates/router/src/compatibility/stripe/app.rs +++ b/crates/router/src/compatibility/stripe/app.rs @@ -1,7 +1,9 @@ -use actix_web::{web, Scope}; - +#[cfg(not(feature = "v2"))] use super::{customers::*, payment_intents::*, refunds::*, setup_intents::*, webhooks::*}; +#[cfg(feature = "v2")] +use super::{payment_intents::*, refunds::*, setup_intents::*, webhooks::*}; use crate::routes::{self, mandates, webhooks}; +use actix_web::{web, Scope}; pub struct PaymentIntents; diff --git a/crates/router/src/compatibility/stripe/customers.rs b/crates/router/src/compatibility/stripe/customers.rs index 5c2cba802da..75fc12b78ff 100644 --- a/crates/router/src/compatibility/stripe/customers.rs +++ b/crates/router/src/compatibility/stripe/customers.rs @@ -4,6 +4,7 @@ use common_utils::id_type; use error_stack::report; use router_env::{instrument, tracing, Flow}; +#[cfg(not(feature = "v2"))] use crate::{ compatibility::{stripe::errors, wrap}, core::{api_locking, customers, payment_methods::cards}, @@ -12,6 +13,15 @@ use crate::{ types::api::{customers as customer_types, payment_methods}, }; +#[cfg(feature = "v2")] +use crate::{ + compatibility::{stripe::errors, wrap}, + core::{api_locking, customers}, + routes, + services::{api, authentication as auth}, + types::api::customers as customer_types, +}; + #[instrument(skip_all, fields(flow = ?Flow::CustomersCreate))] pub async fn customer_create( state: web::Data, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index a00c00a806b..d95a09ee62b 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3595,7 +3595,7 @@ pub async fn list_customer_payment_method( get_bank_from_hs_locker( state, &key_store, - Some(parent_payment_method_token.clone()), + Some(&parent_payment_method_token), &pm.customer_id, &pm.merchant_id, pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id), @@ -3735,6 +3735,27 @@ pub async fn list_customer_payment_method( customer_payment_methods: customer_pms, is_guest_customer: payment_intent.as_ref().map(|_| false), //to return this key only when the request is tied to a payment intent }; + + Box::pin(perform_surcharge_ops( + payment_intent, + state, + merchant_account, + key_store, + &mut response, + )) + .await?; + + Ok(services::ApplicationResponse::Json(response)) +} + +async fn perform_surcharge_ops( + payment_intent: Option, + state: &routes::SessionState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + response: &mut api::CustomerPaymentMethodsListResponse, +) -> Result<(), error_stack::Report> { + let db = &*state.store; let payment_attempt = payment_intent .as_ref() .async_map(|payment_intent| async { @@ -3751,7 +3772,6 @@ pub async fn list_customer_payment_method( }) .await .transpose()?; - let profile_id = payment_intent .as_ref() .async_map(|payment_intent| async { @@ -3774,7 +3794,6 @@ pub async fn list_customer_payment_method( &merchant_account.merchant_id, ) .await?; - if let Some((payment_attempt, payment_intent, business_profile)) = payment_attempt .zip(payment_intent) .zip(business_profile) @@ -3787,12 +3806,12 @@ pub async fn list_customer_payment_method( &business_profile, &payment_attempt, payment_intent, - &mut response, + response, ) .await?; } - Ok(services::ApplicationResponse::Json(response)) + Ok(()) } #[cfg(feature = "v2")] @@ -3986,10 +4005,10 @@ pub async fn list_customer_payment_method( let pmd = if let Some(card) = payment_method_retrieval_context.card_details { Some(api::PaymentMethodListData::Card(card)) - } else if let Some(bank) = payment_method_retrieval_context.bank_transfer_details { - Some(api::PaymentMethodListData::Bank(bank)) } else { - None + payment_method_retrieval_context + .bank_transfer_details + .map(api::PaymentMethodListData::Bank) }; // Need validation for enabled payment method ,querying MCA let pma = api::CustomerPaymentMethod { @@ -4077,65 +4096,15 @@ pub async fn list_customer_payment_method( customer_payment_methods: customer_pms, is_guest_customer: payment_intent.as_ref().map(|_| false), //to return this key only when the request is tied to a payment intent }; - let payment_attempt = if is_payment_associated { - payment_intent - .as_ref() - .async_map(|payment_intent| async { - state - .store - .find_payment_attempt_by_payment_id_merchant_id_attempt_id( - &payment_intent.payment_id, - &merchant_account.merchant_id, - &payment_intent.active_attempt.get_id(), - merchant_account.storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) - }) - .await - .transpose()? - } else { - None - }; - let profile_id = payment_intent - .as_ref() - .async_map(|payment_intent| async { - crate::core::utils::get_profile_id_from_business_details( - payment_intent.business_country, - payment_intent.business_label.as_ref(), - &merchant_account, - payment_intent.profile_id.as_ref(), - db, - false, - ) - .await - .attach_printable("Could not find profile id from business details") - }) - .await - .transpose()?; - - let business_profile = core_utils::validate_and_get_business_profile( - db, - profile_id.as_ref(), - &merchant_account.merchant_id, - ) - .await?; - - if let Some((payment_attempt, payment_intent, business_profile)) = payment_attempt - .zip(payment_intent) - .zip(business_profile) - .map(|((pa, pi), bp)| (pa, pi, bp)) - { - call_surcharge_decision_management_for_saved_card( - state, - &merchant_account, - &key_store, - &business_profile, - &payment_attempt, + if is_payment_associated { + Box::pin(perform_surcharge_ops( payment_intent, + state, + merchant_account, + key_store, &mut response, - ) + )) .await?; } diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index a9ed7f6cb77..c755ca72937 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -3,6 +3,9 @@ use api_models::{ payments, routing, surcharge_decision_configs::{self, SurchargeDecisionConfigs, SurchargeDecisionManagerRecord}, }; +#[cfg(not(feature = "v2"))] +use common_utils::{ext_traits::StringExt, types as common_utils_types}; +#[cfg(feature = "v2")] use common_utils::{ ext_traits::{OptionExt, StringExt}, types as common_utils_types, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 17ea4261185..2f22aceea2f 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -34,8 +34,10 @@ use super::{ files::*, gsm::*, payment_link::*, user::*, user_role::*, webhook_events::*, }; use super::{cache::*, health::*}; -#[cfg(any(feature = "olap", feature = "oltp"))] +#[cfg(all(any(feature = "olap", feature = "oltp"), not(feature = "v2")))] use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; +#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v2"))] +use super::{configs::*, mandates::*, payments::*, refunds::*}; #[cfg(any(feature = "olap", feature = "oltp"))] use super::{currency, payment_methods::*}; #[cfg(feature = "oltp")] From 47874b313f5fa9ccae82e9a281d3bfe19244a1b2 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 2 Jul 2024 15:34:10 +0530 Subject: [PATCH 12/30] fix(payment_methods): Fixed errors --- .../router/src/core/payment_methods/cards.rs | 42 ++++++++++--------- .../router/src/types/api/payment_methods.rs | 5 ++- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 3cc426a37e4..7a2e5f97924 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4094,8 +4094,6 @@ pub async fn list_customer_payment_method( }; customer_pms.push(pma.to_owned()); - let intent_created = payment_intent.as_ref().map(|intent| intent.created_at); - if is_payment_associated { let token = parent_payment_method_token .as_ref() @@ -4106,8 +4104,13 @@ pub async fn list_customer_payment_method( .cloned() .get_required_value("PaymentTokenData")?; + let intent_fulfillment_time = business_profile + .as_ref() + .and_then(|b_profile| b_profile.intent_fulfillment_time) + .unwrap_or(consts::DEFAULT_INTENT_FULFILLMENT_TIME); + ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method)) - .insert(intent_created, hyperswitch_token_data, state) + .insert(intent_fulfillment_time, hyperswitch_token_data, state) .await?; if let Some(metadata) = pma.metadata { @@ -4118,29 +4121,28 @@ pub async fn list_customer_payment_method( "Failed to deserialize metadata to PaymentmethodMetadata struct", )?; + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + + for pm_metadata in pm_metadata_vec.payment_method_tokenization { + let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata + .parse_value("PaymentMethodMetadata") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Failed to deserialize metadata to PaymentmethodMetadata struct", + )?; + for pm_metadata in pm_metadata_vec.payment_method_tokenization { let key = format!( "pm_token_{}_{}_{}", - token, pma.payment_method, pm_metadata.0 + parent_payment_method_token, pma.payment_method, pm_metadata.0 ); - let current_datetime_utc = common_utils::date_time::now(); - let time_elapsed = current_datetime_utc - - payment_intent - .as_ref() - .map(|intent| intent.created_at) - .unwrap_or_else(|| current_datetime_utc); - let redis_conn = state - .store - .get_redis_conn() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get redis connection")?; redis_conn - .set_key_with_expiry( - &key, - pm_metadata.1, - consts::TOKEN_TTL - time_elapsed.whole_seconds(), - ) + .set_key_with_expiry(&key, pm_metadata.1, intent_fulfillment_time) .await .change_context(errors::StorageError::KVError) .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 7fd4c6226d2..31b9f24e9ab 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -3,8 +3,9 @@ pub use api_models::payment_methods::{ CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, - PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, - PaymentMethodList, PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, + PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, + PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList, + PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, From 7a918cd30f0189af60a0bc24a46379cdaa4e6eb0 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 2 Jul 2024 15:39:48 +0530 Subject: [PATCH 13/30] fix(payment_methods): Fixed errors --- crates/router/src/compatibility/stripe/app.rs | 3 +- .../src/compatibility/stripe/customers.rs | 13 +++---- .../router/src/core/payment_methods/cards.rs | 37 ++++++++++--------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/crates/router/src/compatibility/stripe/app.rs b/crates/router/src/compatibility/stripe/app.rs index 19d1f5aea5e..c1a0012fc26 100644 --- a/crates/router/src/compatibility/stripe/app.rs +++ b/crates/router/src/compatibility/stripe/app.rs @@ -1,9 +1,10 @@ +use actix_web::{web, Scope}; + #[cfg(not(feature = "v2"))] use super::{customers::*, payment_intents::*, refunds::*, setup_intents::*, webhooks::*}; #[cfg(feature = "v2")] use super::{payment_intents::*, refunds::*, setup_intents::*, webhooks::*}; use crate::routes::{self, mandates, webhooks}; -use actix_web::{web, Scope}; pub struct PaymentIntents; diff --git a/crates/router/src/compatibility/stripe/customers.rs b/crates/router/src/compatibility/stripe/customers.rs index 75fc12b78ff..7cd6f96cac7 100644 --- a/crates/router/src/compatibility/stripe/customers.rs +++ b/crates/router/src/compatibility/stripe/customers.rs @@ -4,22 +4,21 @@ use common_utils::id_type; use error_stack::report; use router_env::{instrument, tracing, Flow}; -#[cfg(not(feature = "v2"))] +#[cfg(feature = "v2")] use crate::{ compatibility::{stripe::errors, wrap}, - core::{api_locking, customers, payment_methods::cards}, + core::{api_locking, customers}, routes, services::{api, authentication as auth}, - types::api::{customers as customer_types, payment_methods}, + types::api::customers as customer_types, }; - -#[cfg(feature = "v2")] +#[cfg(not(feature = "v2"))] use crate::{ compatibility::{stripe::errors, wrap}, - core::{api_locking, customers}, + core::{api_locking, customers, payment_methods::cards}, routes, services::{api, authentication as auth}, - types::api::customers as customer_types, + types::api::{customers as customer_types, payment_methods}, }; #[instrument(skip_all, fields(flow = ?Flow::CustomersCreate))] diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 7a2e5f97924..24d35789954 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4121,7 +4121,7 @@ pub async fn list_customer_payment_method( "Failed to deserialize metadata to PaymentmethodMetadata struct", )?; - let redis_conn = state + let redis_conn = state .store .get_redis_conn() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -4129,24 +4129,25 @@ pub async fn list_customer_payment_method( for pm_metadata in pm_metadata_vec.payment_method_tokenization { let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata - .parse_value("PaymentMethodMetadata") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Failed to deserialize metadata to PaymentmethodMetadata struct", - )?; - - for pm_metadata in pm_metadata_vec.payment_method_tokenization { - let key = format!( - "pm_token_{}_{}_{}", - parent_payment_method_token, pma.payment_method, pm_metadata.0 - ); - - redis_conn - .set_key_with_expiry(&key, pm_metadata.1, intent_fulfillment_time) - .await - .change_context(errors::StorageError::KVError) + .parse_value("PaymentMethodMetadata") .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add data in redis")?; + .attach_printable( + "Failed to deserialize metadata to PaymentmethodMetadata struct", + )?; + + for pm_metadata in pm_metadata_vec.payment_method_tokenization { + let key = format!( + "pm_token_{}_{}_{}", + parent_payment_method_token, pma.payment_method, pm_metadata.0 + ); + + redis_conn + .set_key_with_expiry(&key, pm_metadata.1, intent_fulfillment_time) + .await + .change_context(errors::StorageError::KVError) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add data in redis")?; + } } } } From a921a0661caff53a303c8002f4c4a009199dd217 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 2 Jul 2024 15:57:57 +0530 Subject: [PATCH 14/30] fix(payment_methods): Fixed errors --- crates/router/src/core/payment_methods/cards.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 24d35789954..5b072978b54 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4138,7 +4138,7 @@ pub async fn list_customer_payment_method( for pm_metadata in pm_metadata_vec.payment_method_tokenization { let key = format!( "pm_token_{}_{}_{}", - parent_payment_method_token, pma.payment_method, pm_metadata.0 + token, pma.payment_method, pm_metadata.0 ); redis_conn From dbafd56bddc0d7aa6912ab5037cebd7b0dfa993b Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 2 Jul 2024 19:58:24 +0530 Subject: [PATCH 15/30] fix(payment_methods): Fixed errors --- .../router/src/core/payment_methods/cards.rs | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 5b072978b54..7b3784b50f3 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4128,26 +4128,17 @@ pub async fn list_customer_payment_method( .attach_printable("Failed to get redis connection")?; for pm_metadata in pm_metadata_vec.payment_method_tokenization { - let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata - .parse_value("PaymentMethodMetadata") + let key = format!( + "pm_token_{}_{}_{}", + token, pma.payment_method, pm_metadata.0 + ); + + redis_conn + .set_key_with_expiry(&key, pm_metadata.1, intent_fulfillment_time) + .await + .change_context(errors::StorageError::KVError) .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Failed to deserialize metadata to PaymentmethodMetadata struct", - )?; - - for pm_metadata in pm_metadata_vec.payment_method_tokenization { - let key = format!( - "pm_token_{}_{}_{}", - token, pma.payment_method, pm_metadata.0 - ); - - redis_conn - .set_key_with_expiry(&key, pm_metadata.1, intent_fulfillment_time) - .await - .change_context(errors::StorageError::KVError) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add data in redis")?; - } + .attach_printable("Failed to add data in redis")?; } } } From ff414c70d563870efe2ced07ad63779097d2d124 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 3 Jul 2024 15:57:58 +0530 Subject: [PATCH 16/30] refactor(payment_methods): Refactored a bit --- crates/api_models/src/payment_methods.rs | 1 + .../router/src/core/payment_methods/cards.rs | 320 ++++++++---------- .../src/types/storage/payment_method.rs | 10 - 3 files changed, 137 insertions(+), 194 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 22400bc05a4..9b2d564b9af 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -950,6 +950,7 @@ pub struct CustomerPaymentMethod { #[cfg(feature = "v2")] #[derive(Debug, Clone, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] pub enum PaymentMethodListData { Card(CardDetailFromLocker), #[cfg(feature = "payouts")] diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 7b3784b50f3..16ae8404a79 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -43,6 +43,8 @@ use super::surcharge_decision_configs::{ }; #[cfg(not(feature = "connector_choice_mca_id"))] use crate::core::utils::get_connector_label; +#[cfg(not(feature = "v2"))] +use crate::routes::app::SessionStateInfo; use crate::{ configs::settings, core::{ @@ -58,7 +60,7 @@ use crate::{ }, db, logger, pii::prelude::*, - routes::{self, app::SessionStateInfo, metrics, payment_methods::ParentPaymentMethodToken}, + routes::{self, metrics, payment_methods::ParentPaymentMethodToken}, services, types::{ api::{self, routing as routing_types, PaymentMethodCreateExt}, @@ -3597,86 +3599,21 @@ pub async fn list_customer_payment_method( let payment_method = pm.payment_method.get_required_value("payment_method")?; - let payment_method_retrieval_context = match payment_method { - enums::PaymentMethod::Card => { - let card_details = get_card_details_with_locker_fallback(&pm, key, state).await?; - - if card_details.is_some() { - PaymentMethodListContext { - card_details, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: PaymentTokenData::permanent_card( - Some(pm.payment_method_id.clone()), - pm.locker_id.clone().or(Some(pm.payment_method_id.clone())), - pm.locker_id.clone().unwrap_or(pm.payment_method_id.clone()), - ), - } - } else { - continue; - } - } - - enums::PaymentMethod::BankDebit => { - // Retrieve the pm_auth connector details so that it can be tokenized - let bank_account_token_data = get_bank_account_connector_details(&pm, key) - .await - .unwrap_or_else(|err| { - logger::error!(error=?err); - None - }); - if let Some(data) = bank_account_token_data { - let token_data = PaymentTokenData::AuthBankDebit(data); - - PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: token_data, - } - } else { - continue; - } - } + let pm_list_context = get_pm_list_context( + state, + &payment_method, + &key_store, + &pm, + Some(parent_payment_method_token.clone()), + true, + ) + .await?; - enums::PaymentMethod::Wallet => PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: PaymentTokenData::wallet_token( - pm.payment_method_id.clone(), - ), - }, + if pm_list_context.is_none() { + continue; + } - #[cfg(feature = "payouts")] - enums::PaymentMethod::BankTransfer => PaymentMethodListContext { - card_details: None, - bank_transfer_details: Some( - get_bank_from_hs_locker( - state, - &key_store, - Some(&parent_payment_method_token), - &pm.customer_id, - &pm.merchant_id, - pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id), - ) - .await?, - ), - hyperswitch_token_data: PaymentTokenData::temporary_generic( - parent_payment_method_token.clone(), - ), - }, - - _ => PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: PaymentTokenData::temporary_generic(generate_id( - consts::ID_LENGTH, - "token", - )), - }, - }; + let pm_list_context = pm_list_context.get_required_value("PaymentMethodListContext")?; // Retrieve the masked bank details to be sent as a response let bank_details = if payment_method == enums::PaymentMethod::BankDebit { @@ -3720,7 +3657,7 @@ pub async fn list_customer_payment_method( payment_method, payment_method_type: pm.payment_method_type, payment_method_issuer: pm.payment_method_issuer, - card: payment_method_retrieval_context.card_details, + card: pm_list_context.card_details, metadata: pm.metadata, payment_method_issuer_code: pm.payment_method_issuer_code, recurring_enabled: mca_enabled, @@ -3728,7 +3665,7 @@ pub async fn list_customer_payment_method( payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), created: Some(pm.created_at), #[cfg(feature = "payouts")] - bank_transfer: payment_method_retrieval_context.bank_transfer_details, + bank_transfer: pm_list_context.bank_transfer_details, bank: bank_details, surcharge_details: None, requires_cvv: requires_cvv @@ -3751,15 +3688,15 @@ pub async fn list_customer_payment_method( .and_then(|b_profile| b_profile.intent_fulfillment_time) .unwrap_or(consts::DEFAULT_INTENT_FULFILLMENT_TIME); + let hyperswitch_token_data = pm_list_context + .hyperswitch_token_data + .get_required_value("PaymentTokenData")?; + ParentPaymentMethodToken::create_key_for_token(( &parent_payment_method_token, pma.payment_method, )) - .insert( - intent_fulfillment_time, - payment_method_retrieval_context.hyperswitch_token_data, - state, - ) + .insert(intent_fulfillment_time, hyperswitch_token_data, state) .await?; if let Some(metadata) = pma.metadata { @@ -3804,6 +3741,94 @@ pub async fn list_customer_payment_method( Ok(services::ApplicationResponse::Json(response)) } +async fn get_pm_list_context( + state: &routes::SessionState, + payment_method: &enums::PaymentMethod, + key_store: &domain::MerchantKeyStore, + pm: &diesel_models::PaymentMethod, + parent_payment_method_token: Option, + is_payment_associated: bool, +) -> Result, error_stack::Report> { + let key = key_store.key.get_inner().peek(); + + let payment_method_retrieval_context = match payment_method { + enums::PaymentMethod::Card => { + let card_details = get_card_details_with_locker_fallback(pm, key, state).await?; + + card_details.as_ref().map(|card| PaymentMethodListContext { + card_details: Some(card.clone()), + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: is_payment_associated.then_some( + PaymentTokenData::permanent_card( + Some(pm.payment_method_id.clone()), + pm.locker_id.clone().or(Some(pm.payment_method_id.clone())), + pm.locker_id.clone().unwrap_or(pm.payment_method_id.clone()), + ), + ), + }) + } + + enums::PaymentMethod::BankDebit => { + // Retrieve the pm_auth connector details so that it can be tokenized + let bank_account_token_data = get_bank_account_connector_details(pm, key) + .await + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }); + + bank_account_token_data.map(|data| { + let token_data = PaymentTokenData::AuthBankDebit(data); + + PaymentMethodListContext { + card_details: None, + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: is_payment_associated.then_some(token_data), + } + }) + } + + enums::PaymentMethod::Wallet => Some(PaymentMethodListContext { + card_details: None, + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: is_payment_associated + .then_some(PaymentTokenData::wallet_token(pm.payment_method_id.clone())), + }), + + #[cfg(feature = "payouts")] + enums::PaymentMethod::BankTransfer => Some(PaymentMethodListContext { + card_details: None, + bank_transfer_details: Some( + get_bank_from_hs_locker( + state, + key_store, + parent_payment_method_token.as_ref(), + &pm.customer_id, + &pm.merchant_id, + pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id), + ) + .await?, + ), + hyperswitch_token_data: parent_payment_method_token + .map(|token| PaymentTokenData::temporary_generic(token.clone())), + }), + + _ => Some(PaymentMethodListContext { + card_details: None, + #[cfg(feature = "payouts")] + bank_transfer_details: None, + hyperswitch_token_data: is_payment_associated.then_some( + PaymentTokenData::temporary_generic(generate_id(consts::ID_LENGTH, "token")), + ), + }), + }; + + Ok(payment_method_retrieval_context) +} + async fn perform_surcharge_ops( payment_intent: Option, state: &routes::SessionState, @@ -3935,106 +3960,35 @@ pub async fn list_customer_payment_method( let mut customer_pms = Vec::new(); for pm in resp.into_iter() { let payment_method = pm.payment_method.get_required_value("payment_method")?; - let mut bank_details = None; let parent_payment_method_token = is_payment_associated.then(|| generate_id(consts::ID_LENGTH, "token")); - let payment_method_retrieval_context = match payment_method { - enums::PaymentMethod::Card => { - let card_details = get_card_details_with_locker_fallback(&pm, key, state).await?; - - if card_details.is_some() { - PaymentMethodListContext { - card_details, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: if is_payment_associated { - Some(PaymentTokenData::permanent_card( - Some(pm.payment_method_id.clone()), - pm.locker_id.clone().or(Some(pm.payment_method_id.clone())), - pm.locker_id.clone().unwrap_or(pm.payment_method_id.clone()), - )) - } else { - None - }, - } - } else { - continue; - } - } - - enums::PaymentMethod::BankDebit => { - // Retrieve the pm_auth connector details so that it can be tokenized - let bank_account_token_data = if is_payment_associated { - get_bank_account_connector_details(&pm, key) - .await - .unwrap_or_else(|err| { - logger::error!(error=?err); - None - }) - } else { - None - }; - - // Retrieve the masked bank details to be sent as a response - bank_details = if payment_method == enums::PaymentMethod::BankDebit { - get_masked_bank_details(&pm, key) - .await - .unwrap_or_else(|err| { - logger::error!(error=?err); - None - }) - } else { - None - }; - - PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: bank_account_token_data - .map(PaymentTokenData::AuthBankDebit), - } - } + let pm_list_context = get_pm_list_context( + state, + &payment_method, + &key_store, + &pm, + parent_payment_method_token.clone(), + is_payment_associated, + ) + .await?; - enums::PaymentMethod::Wallet => PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: Some(PaymentTokenData::wallet_token( - pm.payment_method_id.clone(), - )), - }, + if pm_list_context.is_none() { + continue; + } - #[cfg(feature = "payouts")] - enums::PaymentMethod::BankTransfer => PaymentMethodListContext { - card_details: None, - bank_transfer_details: Some( - get_bank_from_hs_locker( - state, - &key_store, - parent_payment_method_token.as_ref(), - &pm.customer_id, - &pm.merchant_id, - pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id), - ) - .await?, - ), - hyperswitch_token_data: parent_payment_method_token - .as_ref() - .map(|token| PaymentTokenData::temporary_generic(token.clone())), - }, + let pm_list_context = pm_list_context.get_required_value("PaymentMethodListContext")?; - _ => PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: Some(PaymentTokenData::temporary_generic(generate_id( - consts::ID_LENGTH, - "token", - ))), - }, + let bank_details = if payment_method == enums::PaymentMethod::BankDebit { + get_masked_bank_details(&pm, key) + .await + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }) + } else { + None }; let payment_method_billing = decrypt_generic_data::( @@ -4061,10 +4015,10 @@ pub async fn list_customer_payment_method( ) .await?; - let pmd = if let Some(card) = payment_method_retrieval_context.card_details { + let pmd = if let Some(card) = pm_list_context.card_details { Some(api::PaymentMethodListData::Card(card)) } else { - payment_method_retrieval_context + pm_list_context .bank_transfer_details .map(api::PaymentMethodListData::Bank) }; @@ -4098,10 +4052,8 @@ pub async fn list_customer_payment_method( let token = parent_payment_method_token .as_ref() .get_required_value("parent_payment_method_token")?; - let hyperswitch_token_data = payment_method_retrieval_context + let hyperswitch_token_data = pm_list_context .hyperswitch_token_data - .as_ref() - .cloned() .get_required_value("PaymentTokenData")?; let intent_fulfillment_time = business_profile diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs index f6dd7826320..16fa5575b23 100644 --- a/crates/router/src/types/storage/payment_method.rs +++ b/crates/router/src/types/storage/payment_method.rs @@ -78,16 +78,6 @@ impl PaymentTokenData { } } -#[cfg(not(feature = "v2"))] -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PaymentMethodListContext { - pub card_details: Option, - pub hyperswitch_token_data: PaymentTokenData, - #[cfg(feature = "payouts")] - pub bank_transfer_details: Option, -} - -#[cfg(feature = "v2")] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentMethodListContext { pub card_details: Option, From f991fcacaa7a6bfe48c597fbfa0ab33bf4beffb6 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 3 Jul 2024 16:58:10 +0530 Subject: [PATCH 17/30] fix(payment_methods): Fixed errors --- crates/router/src/core/payment_methods/cards.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 2c224cde325..6cbb691609e 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3763,7 +3763,8 @@ async fn get_pm_list_context( payment_method: &enums::PaymentMethod, key_store: &domain::MerchantKeyStore, pm: &diesel_models::PaymentMethod, - parent_payment_method_token: Option, + #[cfg(feature = "payouts")] parent_payment_method_token: Option, + #[cfg(not(feature = "payouts"))] _parent_payment_method_token: Option, is_payment_associated: bool, ) -> Result, error_stack::Report> { let key = key_store.key.get_inner().peek(); From c4ffdddd6570af6c22d51f8e0a107552a7b720ab Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 3 Jul 2024 18:48:38 +0530 Subject: [PATCH 18/30] fix(payment_methods): Fixed errors --- crates/router/src/core/payment_methods/cards.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 6cbb691609e..bb7afc79525 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4033,14 +4033,15 @@ pub async fn list_customer_payment_method( ) .await?; - let pmd = if let Some(card) = pm_list_context.card_details { - Some(api::PaymentMethodListData::Card(card)) - } else { - pm_list_context - .bank_transfer_details - .map(api::PaymentMethodListData::Bank) - }; - // Need validation for enabled payment method ,querying MCA + #[cfg(not(feature = "payouts"))] + let pmd = pm_list_context + .card_details + .map(api::PaymentMethodListData::Card); + #[cfg(feature = "payouts")] + let pmd = pm_list_context + .bank_transfer_details + .map(api::PaymentMethodListData::Bank); + let pma = api::CustomerPaymentMethod { payment_token: parent_payment_method_token.clone(), payment_method_id: pm.payment_method_id.clone(), From 761dca6b482ec253ae103a867be886a2caedf9c6 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Mon, 22 Jul 2024 20:17:08 +0530 Subject: [PATCH 19/30] refactor(payment_methods): Refactored --- crates/api_models/Cargo.toml | 1 + crates/api_models/src/payment_methods.rs | 16 +- crates/router/Cargo.toml | 1 + .../router/src/core/payment_methods/cards.rs | 471 +++++++++++------- crates/router/src/routes/app.rs | 47 +- crates/router/src/routes/payment_methods.rs | 21 +- .../router/src/types/api/payment_methods.rs | 13 +- 7 files changed, 358 insertions(+), 212 deletions(-) diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index aced6a293ab..0fcfe774a03 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -21,6 +21,7 @@ v2 = [] v1 = [] merchant_account_v2 = [] payment_v2 = [] +payment_methods_v2 = [] [dependencies] actix-web = { version = "4.5.1", optional = true } diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 1fed061bb1b..0e3046fbbcf 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -992,7 +992,10 @@ impl serde::Serialize for PaymentMethodList { } } -#[cfg(not(feature = "v2"))] +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, serde::Serialize, ToSchema)] pub struct CustomerPaymentMethodsListResponse { /// List of payment methods for customer @@ -1001,7 +1004,7 @@ pub struct CustomerPaymentMethodsListResponse { pub is_guest_customer: Option, } -#[cfg(feature = "v2")] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Serialize, ToSchema)] pub struct CustomerPaymentMethodsListResponse { /// List of payment methods for customer @@ -1036,7 +1039,7 @@ pub struct CustomerDefaultPaymentMethodResponse { pub payment_method_type: Option, } -#[cfg(feature = "v2")] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, Clone, serde::Serialize, ToSchema)] pub struct CustomerPaymentMethod { /// Token for payment method in temporary card locker which gets refreshed often @@ -1114,7 +1117,7 @@ pub struct CustomerPaymentMethod { pub billing: Option, } -#[cfg(feature = "v2")] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, Clone, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum PaymentMethodListData { @@ -1123,7 +1126,10 @@ pub enum PaymentMethodListData { Bank(payouts::Bank), } -#[cfg(not(feature = "v2"))] +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, Clone, serde::Serialize, ToSchema)] pub struct CustomerPaymentMethod { /// Token for payment method in temporary card locker which gets refreshed often diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 743a94c942a..0a2de2693ac 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -34,6 +34,7 @@ v2 = ["api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "stor v1 = ["api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1"] merchant_account_v2 = ["api_models/merchant_account_v2", "diesel_models/merchant_account_v2", "hyperswitch_domain_models/merchant_account_v2"] payment_v2 = ["api_models/payment_v2", "diesel_models/payment_v2", "hyperswitch_domain_models/payment_v2"] +payment_methods_v2 = ["api_models/payment_methods_v2"] [dependencies] actix-cors = "0.6.5" diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 6b31d689ce0..24fafa616cf 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -50,7 +50,10 @@ use super::surcharge_decision_configs::{ }; #[cfg(not(feature = "connector_choice_mca_id"))] use crate::core::utils::get_connector_label; -#[cfg(not(feature = "v2"))] +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] use crate::routes::app::SessionStateInfo; #[cfg(feature = "payouts")] use crate::types::domain::types::AsyncLift; @@ -3618,7 +3621,7 @@ fn filter_recurring_based( recurring_enabled.map_or(true, |enabled| payment_method.recurring_enabled == enabled) } -#[cfg(feature = "v2")] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn list_customer_payment_method_util( state: routes::SessionState, merchant_account: domain::MerchantAccount, @@ -3627,13 +3630,12 @@ pub async fn list_customer_payment_method_util( customer_id: Option, is_payment_associated: bool, ) -> errors::RouterResponse { - let db = state.store.as_ref(); let limit = req.as_ref().and_then(|pml_req| pml_req.limit); let (customer_id, payment_intent) = if is_payment_associated { let cloned_secret = req.and_then(|r| r.client_secret.clone()); let payment_intent = helpers::verify_payment_intent_time_and_client_secret( - db, + &state, &merchant_account, &key_store, cloned_secret, @@ -3672,7 +3674,10 @@ pub async fn list_customer_payment_method_util( Ok(resp) } -#[cfg(not(feature = "v2"))] +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] pub async fn do_list_customer_pm_fetch_customer_if_not_passed( state: routes::SessionState, merchant_account: domain::MerchantAccount, @@ -3744,7 +3749,10 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( } } -#[cfg(not(feature = "v2"))] +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] pub async fn list_customer_payment_method( state: &routes::SessionState, merchant_account: domain::MerchantAccount, @@ -3995,11 +4003,9 @@ async fn get_pm_list_context( #[cfg(not(feature = "payouts"))] _parent_payment_method_token: Option, is_payment_associated: bool, ) -> Result, error_stack::Report> { - let key = key_store.key.get_inner().peek(); - let payment_method_retrieval_context = match payment_method { enums::PaymentMethod::Card => { - let card_details = get_card_details_with_locker_fallback(pm, key, state).await?; + let card_details = get_card_details_with_locker_fallback(pm, state, key_store).await?; card_details.as_ref().map(|card| PaymentMethodListContext { card_details: Some(card.clone()), @@ -4017,7 +4023,7 @@ async fn get_pm_list_context( enums::PaymentMethod::BankDebit => { // Retrieve the pm_auth connector details so that it can be tokenized - let bank_account_token_data = get_bank_account_connector_details(pm, key) + let bank_account_token_data = get_bank_account_connector_details(state, pm, key_store) .await .unwrap_or_else(|err| { logger::error!(error=?err); @@ -4119,7 +4125,128 @@ async fn perform_surcharge_ops( Ok(()) } -#[cfg(feature = "v2")] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +struct SavedPMLPaymentsInfo { + pub payment_intent: storage::PaymentIntent, + pub business_profile: Option, + pub requires_cvv: bool, + pub off_session_payment_flag: bool, + pub is_connector_agnostic_mit_enabled: bool, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl SavedPMLPaymentsInfo { + pub async fn form_payments_info( + payment_intent: storage::PaymentIntent, + merchant_account: &domain::MerchantAccount, + db: &dyn db::StorageInterface, + ) -> errors::RouterResult { + let requires_cvv = db + .find_config_by_key_unwrap_or( + format!("{}_requires_cvv", merchant_account.merchant_id).as_str(), + Some("true".to_string()), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch requires_cvv config")? + .config + != "false"; + + let off_session_payment_flag = matches!( + payment_intent.setup_future_usage, + Some(common_enums::FutureUsage::OffSession) + ); + + let profile_id = core_utils::get_profile_id_from_business_details( + payment_intent.business_country, + payment_intent.business_label.as_ref(), + &merchant_account, + payment_intent.profile_id.as_ref(), + db, + false, + ) + .await + .attach_printable("Could not find profile id from business details")?; + + let business_profile = core_utils::validate_and_get_business_profile( + db, + Some(profile_id).as_ref(), + &merchant_account.merchant_id, + ) + .await?; + + let is_connector_agnostic_mit_enabled = business_profile + .as_ref() + .and_then(|business_profile| business_profile.is_connector_agnostic_mit_enabled) + .unwrap_or(false); + + Ok(Self { + payment_intent, + business_profile, + requires_cvv, + off_session_payment_flag, + is_connector_agnostic_mit_enabled, + }) + } + + pub async fn perform_payment_ops( + &self, + state: &routes::SessionState, + parent_payment_method_token: Option, + pma: &api::CustomerPaymentMethod, + pm_list_context: PaymentMethodListContext, + ) -> errors::RouterResult<()> { + let token = parent_payment_method_token + .as_ref() + .get_required_value("parent_payment_method_token")?; + let hyperswitch_token_data = pm_list_context + .hyperswitch_token_data + .get_required_value("PaymentTokenData")?; + + let intent_fulfillment_time = self + .business_profile + .as_ref() + .and_then(|b_profile| b_profile.intent_fulfillment_time) + .unwrap_or(consts::DEFAULT_INTENT_FULFILLMENT_TIME); + + ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method)) + .insert(intent_fulfillment_time, hyperswitch_token_data, state) + .await?; + + if let Some(metadata) = pma.metadata.clone() { + let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata + .parse_value("PaymentMethodMetadata") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Failed to deserialize metadata to PaymentmethodMetadata struct", + )?; + + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + + for pm_metadata in pm_metadata_vec.payment_method_tokenization { + let key = format!( + "pm_token_{}_{}_{}", + token, pma.payment_method, pm_metadata.0 + ); + + redis_conn + .set_key_with_expiry(&key, pm_metadata.1, intent_fulfillment_time) + .await + .change_context(errors::StorageError::KVError) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add data in redis")?; + } + } + + Ok(()) + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn list_customer_payment_method( state: &routes::SessionState, merchant_account: domain::MerchantAccount, @@ -4130,19 +4257,12 @@ pub async fn list_customer_payment_method( is_payment_associated: bool, ) -> errors::RouterResponse { let db = &*state.store; - let off_session_payment_flag = is_payment_associated - && payment_intent - .as_ref() - .map(|pi| { - matches!( - pi.setup_future_usage, - Some(common_enums::FutureUsage::OffSession) - ) - }) - .unwrap_or(false); + let key_manager_state = &(state).into(); + // let key = key_store.key.get_inner().peek(); let customer = db .find_customer_by_customer_id_merchant_id( + key_manager_state, customer_id, &merchant_account.merchant_id, &key_store, @@ -4151,24 +4271,12 @@ pub async fn list_customer_payment_method( .await .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; - let key = key_store.key.get_inner().peek(); - - let requires_cvv = if is_payment_associated { - let is_requires_cvv = db - .find_config_by_key_unwrap_or( - format!("{}_requires_cvv", merchant_account.merchant_id).as_str(), - Some("true".to_string()), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch requires_cvv config")?; - - is_requires_cvv.config != "false" - } else { - false - }; + let payments_info = payment_intent + .async_map(|pi| SavedPMLPaymentsInfo::form_payments_info(pi, &merchant_account, db)) + .await + .transpose()?; - let resp = db + let saved_payment_methods = db .find_payment_method_by_customer_id_merchant_id_status( customer_id, &merchant_account.merchant_id, @@ -4178,35 +4286,10 @@ pub async fn list_customer_payment_method( ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; - //let mca = query::find_mca_by_merchant_id(conn, &merchant_account.merchant_id)?; - let profile_id = payment_intent - .as_ref() - .async_map(|payment_intent| async { - core_utils::get_profile_id_from_business_details( - payment_intent.business_country, - payment_intent.business_label.as_ref(), - &merchant_account, - payment_intent.profile_id.as_ref(), - db, - false, - ) - .await - .attach_printable("Could not find profile id from business details") - }) - .await - .transpose()?; - let business_profile = core_utils::validate_and_get_business_profile( - db, - profile_id.as_ref(), - &merchant_account.merchant_id, - ) - .await?; - - let mut customer_pms = Vec::new(); - for pm in resp.into_iter() { + let mut filtered_saved_payment_methods_ctx = Vec::new(); + for pm in saved_payment_methods.into_iter() { let payment_method = pm.payment_method.get_required_value("payment_method")?; - let parent_payment_method_token = is_payment_associated.then(|| generate_id(consts::ID_LENGTH, "token")); @@ -4220,141 +4303,45 @@ pub async fn list_customer_payment_method( ) .await?; - if pm_list_context.is_none() { - continue; + if let Some(ctx) = pm_list_context { + filtered_saved_payment_methods_ctx.push((ctx, parent_payment_method_token, pm)); } + } - let pm_list_context = pm_list_context.get_required_value("PaymentMethodListContext")?; - - let bank_details = if payment_method == enums::PaymentMethod::BankDebit { - get_masked_bank_details(&pm, key) - .await - .unwrap_or_else(|err| { - logger::error!(error=?err); - None - }) - } else { - None - }; - - let payment_method_billing = decrypt_generic_data::( - pm.payment_method_billing_address, - key, - ) - .await - .attach_printable("unable to decrypt payment method billing address details")?; - - let connector_mandate_details = pm - .connector_mandate_details - .clone() - .map(|val| { - val.parse_value::("PaymentsMandateReference") - }) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to deserialize to Payment Mandate Reference ")?; - let mca_enabled = get_mca_status( - state, - &key_store, - &merchant_account.merchant_id, - connector_mandate_details, - ) - .await?; - - #[cfg(not(feature = "payouts"))] - let pmd = pm_list_context - .card_details - .map(api::PaymentMethodListData::Card); - #[cfg(feature = "payouts")] - let pmd = pm_list_context - .bank_transfer_details - .map(api::PaymentMethodListData::Bank); - - let pma = api::CustomerPaymentMethod { - payment_token: parent_payment_method_token.clone(), - payment_method_id: pm.payment_method_id.clone(), - customer_id: pm.customer_id, - payment_method, - payment_method_type: pm.payment_method_type, - payment_method_issuer: pm.payment_method_issuer, - payment_method_data: pmd, - metadata: pm.metadata, - payment_method_issuer_code: pm.payment_method_issuer_code, - recurring_enabled: mca_enabled, - installment_payment_enabled: false, - payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), - created: Some(pm.created_at), - bank: bank_details, - surcharge_details: None, - requires_cvv: requires_cvv - && !(off_session_payment_flag && pm.connector_mandate_details.is_some()), - last_used_at: Some(pm.last_used_at), - default_payment_method_set: customer.default_payment_method_id.is_some() - && customer.default_payment_method_id == Some(pm.payment_method_id), - billing: payment_method_billing, - }; - customer_pms.push(pma.to_owned()); - - if is_payment_associated { - let token = parent_payment_method_token - .as_ref() - .get_required_value("parent_payment_method_token")?; - let hyperswitch_token_data = pm_list_context - .hyperswitch_token_data - .get_required_value("PaymentTokenData")?; - - let intent_fulfillment_time = business_profile - .as_ref() - .and_then(|b_profile| b_profile.intent_fulfillment_time) - .unwrap_or(consts::DEFAULT_INTENT_FULFILLMENT_TIME); - - ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method)) - .insert(intent_fulfillment_time, hyperswitch_token_data, state) - .await?; - - if let Some(metadata) = pma.metadata { - let pm_metadata_vec: payment_methods::PaymentMethodMetadata = metadata - .parse_value("PaymentMethodMetadata") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Failed to deserialize metadata to PaymentmethodMetadata struct", - )?; - - let redis_conn = state - .store - .get_redis_conn() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get redis connection")?; + let pm_list_futures = filtered_saved_payment_methods_ctx + .into_iter() + .map(|ctx| { + generate_saved_pm_response( + state, + &key_store, + &merchant_account, + ctx, + &customer, + payments_info.as_ref(), + ) + }) + .collect::>(); - for pm_metadata in pm_metadata_vec.payment_method_tokenization { - let key = format!( - "pm_token_{}_{}_{}", - token, pma.payment_method, pm_metadata.0 - ); + let final_result = futures::future::join_all(pm_list_futures).await; - redis_conn - .set_key_with_expiry(&key, pm_metadata.1, intent_fulfillment_time) - .await - .change_context(errors::StorageError::KVError) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add data in redis")?; - } - } - } + let mut customer_pms = Vec::new(); + for result in final_result.into_iter() { + let pma = result.attach_printable("saved pm list failed")?; + customer_pms.push(pma); } let mut response = api::CustomerPaymentMethodsListResponse { customer_payment_methods: customer_pms, - is_guest_customer: payment_intent.as_ref().map(|_| false), //to return this key only when the request is tied to a payment intent + is_guest_customer: Some(is_payment_associated), //to return this key only when the request is tied to a payment intent }; if is_payment_associated { Box::pin(perform_surcharge_ops( - payment_intent, + payments_info.as_ref().map(|pi| pi.payment_intent.clone()), state, merchant_account, key_store, - business_profile, + payments_info.and_then(|pi| pi.business_profile), &mut response, )) .await?; @@ -4363,6 +4350,120 @@ pub async fn list_customer_payment_method( Ok(services::ApplicationResponse::Json(response)) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +async fn generate_saved_pm_response( + state: &routes::SessionState, + key_store: &domain::MerchantKeyStore, + merchant_account: &domain::MerchantAccount, + pm_list_context: ( + PaymentMethodListContext, + Option, + diesel_models::PaymentMethod, + ), + customer: &domain::Customer, + payment_info: Option<&SavedPMLPaymentsInfo>, +) -> Result> { + let (pm_list_context, parent_payment_method_token, pm) = pm_list_context; + let payment_method = pm.payment_method.get_required_value("payment_method")?; + + let bank_details = if payment_method == enums::PaymentMethod::BankDebit { + get_masked_bank_details(state, &pm, key_store) + .await + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }) + } else { + None + }; + + let payment_method_billing = decrypt_generic_data::( + state, + pm.payment_method_billing_address, + key_store, + ) + .await + .attach_printable("unable to decrypt payment method billing address details")?; + + let connector_mandate_details = pm + .connector_mandate_details + .clone() + .map(|val| val.parse_value::("PaymentsMandateReference")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize to Payment Mandate Reference ")?; + + let (is_connector_agnostic_mit_enabled, requires_cvv, off_session_payment_flag) = payment_info + .map(|pi| { + ( + pi.is_connector_agnostic_mit_enabled, + pi.requires_cvv, + pi.off_session_payment_flag, + ) + }) + .unwrap_or((false, false, false)); + + let mca_enabled = get_mca_status( + state, + key_store, + &merchant_account.merchant_id, + is_connector_agnostic_mit_enabled, + connector_mandate_details, + pm.network_transaction_id.as_ref(), + ) + .await?; + + let requires_cvv = if is_connector_agnostic_mit_enabled { + requires_cvv + && !(off_session_payment_flag + && (pm.connector_mandate_details.is_some() || pm.network_transaction_id.is_some())) + } else { + requires_cvv && !(off_session_payment_flag && pm.connector_mandate_details.is_some()) + }; + #[cfg(not(feature = "payouts"))] + let pmd = pm_list_context + .card_details + .clone() + .map(api::PaymentMethodListData::Card); + #[cfg(feature = "payouts")] + let pmd = pm_list_context + .bank_transfer_details + .clone() + .map(api::PaymentMethodListData::Bank); + let pma = api::CustomerPaymentMethod { + payment_token: parent_payment_method_token.clone(), + payment_method_id: pm.payment_method_id.clone(), + customer_id: pm.customer_id, + payment_method, + payment_method_type: pm.payment_method_type, + payment_method_issuer: pm.payment_method_issuer, + payment_method_data: pmd, + metadata: pm.metadata, + payment_method_issuer_code: pm.payment_method_issuer_code, + recurring_enabled: mca_enabled, + installment_payment_enabled: false, + payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), + created: Some(pm.created_at), + bank: bank_details, + surcharge_details: None, + requires_cvv: requires_cvv + && !(off_session_payment_flag && pm.connector_mandate_details.is_some()), + last_used_at: Some(pm.last_used_at), + default_payment_method_set: customer.default_payment_method_id.is_some() + && customer.default_payment_method_id == Some(pm.payment_method_id), + billing: payment_method_billing, + }; + + payment_info + .async_map(|pi| { + pi.perform_payment_ops(state, parent_payment_method_token, &pma, pm_list_context) + }) + .await + .transpose()?; + + Ok(pma) +} + pub async fn get_mca_status( state: &routes::SessionState, key_store: &domain::MerchantKeyStore, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index f7cab29ee0e..e87e99c5231 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -455,13 +455,33 @@ impl DummyConnector { pub struct Payments; -#[cfg(any(feature = "olap", feature = "oltp"))] +#[cfg(all( + any(feature = "olap", feature = "oltp"), + feature = "v2", + feature = "payment_methods_v2", + feature = "payment_v2" +))] impl Payments { pub fn server(state: AppState) -> Scope { - #[cfg(not(feature = "v2"))] - let mut route = web::scope("/payments").app_data(web::Data::new(state)); - #[cfg(feature = "v2")] let mut route = web::scope("v2/payments").app_data(web::Data::new(state)); + route = route.service( + web::resource("/{payment_id}/saved_payment_methods") + .route(web::get().to(list_customer_payment_method_for_payment)), + ); + + route + } +} + +#[cfg(all( + any(feature = "olap", feature = "oltp"), + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2"), + not(feature = "payment_v2") +))] +impl Payments { + pub fn server(state: AppState) -> Scope { + let mut route = web::scope("/payments").app_data(web::Data::new(state)); #[cfg(feature = "olap")] { @@ -549,13 +569,6 @@ impl Payments { web::resource("/{payment_id}/extended_card_info").route(web::get().to(retrieve_extended_card_info)), ) } - #[cfg(all(feature = "oltp", feature = "v2"))] - { - route = route.service( - web::resource("/{payment_id}/saved_payment_methods") - .route(web::get().to(list_customer_payment_method_for_payment)), - ) - } route } } @@ -802,7 +815,11 @@ impl Routing { pub struct Customers; -#[cfg(all(any(feature = "olap", feature = "oltp"), not(feature = "v2")))] +#[cfg(all( + any(feature = "olap", feature = "oltp"), + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] impl Customers { pub fn server(state: AppState) -> Scope { let mut route = web::scope("customers").app_data(web::Data::new(state)); @@ -845,7 +862,11 @@ impl Customers { } } -#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v2"))] +#[cfg(all( + any(feature = "olap", feature = "oltp"), + feature = "v2", + feature = "payment_methods_v2" +))] impl Customers { pub fn server(state: AppState) -> Scope { let mut route = web::scope("v2/customers").app_data(web::Data::new(state)); diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 8650e27dc38..5c07d6fcfab 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -214,7 +214,11 @@ pub async fn list_payment_method_api( .await } -#[cfg(not(feature = "v2"))] + +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] /// List payment methods for a Customer /// /// To filter and list the applicable payment methods for a particular Customer ID @@ -274,7 +278,10 @@ pub async fn list_customer_payment_method_api( .await } -#[cfg(feature = "v2")] +#[cfg(all( + feature = "v2", + feature = "payment_methods_v2" +))] /// List payment methods for a Customer v2 /// /// To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment @@ -336,7 +343,10 @@ pub async fn list_customer_payment_method_for_payment( .await } -#[cfg(feature = "v2")] +#[cfg(all( + feature = "v2", + feature = "payment_methods_v2" +))] /// List payment methods for a Customer v2 /// /// To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context @@ -397,7 +407,10 @@ pub async fn list_customer_payment_method_api( .await } -#[cfg(not(feature = "v2"))] +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] /// List payment methods for a Customer /// /// To filter and list the applicable payment methods for a particular Customer ID diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 94dd4a28987..062a2fdf630 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "v2")] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub use api_models::payment_methods::{ CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, @@ -6,11 +6,14 @@ pub use api_models::payment_methods::{ PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList, PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, - PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, - TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, - TokenizedWalletValue2, + PaymentMethodMigrate, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, + TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, + TokenizedWalletValue1, TokenizedWalletValue2, }; -#[cfg(not(feature = "v2"))] +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] pub use api_models::payment_methods::{ CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, From 62c91b9d1a729b27c83f9986c7e6137431ac0f1b Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 14:47:54 +0000 Subject: [PATCH 20/30] chore: run formatter --- crates/router/src/routes/payment_methods.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 5c07d6fcfab..683b5c9faa6 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -214,7 +214,6 @@ pub async fn list_payment_method_api( .await } - #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -278,10 +277,7 @@ pub async fn list_customer_payment_method_api( .await } -#[cfg(all( - feature = "v2", - feature = "payment_methods_v2" -))] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] /// List payment methods for a Customer v2 /// /// To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment @@ -343,10 +339,7 @@ pub async fn list_customer_payment_method_for_payment( .await } -#[cfg(all( - feature = "v2", - feature = "payment_methods_v2" -))] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] /// List payment methods for a Customer v2 /// /// To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context From 64fc9a4dcffc483ebda84ec762280a8b5568ac78 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 23 Jul 2024 16:59:11 +0530 Subject: [PATCH 21/30] fix(payment_methods): Fixed errors --- crates/router/Cargo.toml | 2 +- crates/router/src/routes/app.rs | 6 ++---- justfile | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index d3ff4a15aae..589e00654eb 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -118,7 +118,7 @@ x509-parser = "0.16.0" # First party crates analytics = { version = "0.1.0", path = "../analytics", optional = true } -api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] } +api_models = { version = "0.1.0", path = "../api_models", features = ["errors"], default-features = false} cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs", "metrics", "keymanager", "encryption_service"] } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 6b897366922..d060fe52aa8 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -44,10 +44,8 @@ use super::{ files::*, gsm::*, payment_link::*, user::*, user_role::*, webhook_events::*, }; use super::{cache::*, health::*}; -#[cfg(all(any(feature = "olap", feature = "oltp"), not(feature = "v2")))] use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; -#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v2"))] -use super::{configs::*, mandates::*, payments::*, refunds::*}; +// use super::{configs::*, mandates::*, payments::*, refunds::*}; #[cfg(any(feature = "olap", feature = "oltp"))] use super::{currency, payment_methods::*}; #[cfg(feature = "oltp")] @@ -848,7 +846,7 @@ impl Customers { #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "customer_v2"), - not(feature = "payment_methods_v2") + not(feature = "payment_methods_v2"), any(feature = "olap", feature = "oltp") ))] impl Customers { diff --git a/justfile b/justfile index 9b9ad5a8546..7f833012d04 100644 --- a/justfile +++ b/justfile @@ -18,7 +18,7 @@ alias c := check # Check compilation of Rust code and catch common mistakes # We cannot run --all-features because v1 and v2 are mutually exclusive features -# Create a list of features by excluding certain features +# Create a list of features by excluding certain features clippy *FLAGS: #! /usr/bin/env bash set -euo pipefail @@ -27,7 +27,7 @@ clippy *FLAGS: jq -r ' [ ( .workspace_members | sort ) as $package_ids # Store workspace crate package IDs in `package_ids` array | .packages[] | select( IN(.id; $package_ids[]) ) | .features | keys[] ] | unique # Select all unique features from all workspace crates - | del( .[] | select( any( . ; . == ("v2", "merchant_account_v2", "payment_v2", "customer_v2") ) ) ) # Exclude some features from features list + | del( .[] | select( any( . ; . == ("v2", "merchant_account_v2", "payment_v2", "customer_v2", "payment_methods_v2") ) ) ) # Exclude some features from features list | join(",") # Construct a comma-separated string of features for passing to `cargo` ')" From 8595c687074fdd8a24ab82dfecb4b46f4d87746d Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:30:11 +0000 Subject: [PATCH 22/30] chore: run formatter --- crates/router/src/routes/app.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index d060fe52aa8..abe33dec0bb 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -43,8 +43,7 @@ use super::{ admin::*, api_keys::*, apple_pay_certificates_migration, connector_onboarding::*, disputes::*, files::*, gsm::*, payment_link::*, user::*, user_role::*, webhook_events::*, }; -use super::{cache::*, health::*}; -use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; +use super::{cache::*, configs::*, customers::*, health::*, mandates::*, payments::*, refunds::*}; // use super::{configs::*, mandates::*, payments::*, refunds::*}; #[cfg(any(feature = "olap", feature = "oltp"))] use super::{currency, payment_methods::*}; From 4a971ea889c2206d318535e2334d70ddc900e34d Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 23 Jul 2024 17:47:30 +0530 Subject: [PATCH 23/30] fix(payment_methods): Fixed errors --- crates/api_models/src/events/payment.rs | 10 ++++++++-- scripts/ci-checks.sh | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index ad2e2546f37..177f81cac69 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -1,8 +1,14 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; - +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] +use crate::payment_methods::CustomerPaymentMethodsListResponse; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use crate::payment_methods::CustomerPaymentMethodsListResponse; use crate::{ payment_methods::{ - CustomerDefaultPaymentMethodResponse, CustomerPaymentMethodsListResponse, + CustomerDefaultPaymentMethodResponse, DefaultPaymentMethod, ListCountriesCurrenciesRequest, ListCountriesCurrenciesResponse, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCollectLinkResponse, PaymentMethodDeleteResponse, PaymentMethodListRequest, diff --git a/scripts/ci-checks.sh b/scripts/ci-checks.sh index 219bde3c68f..2e4d9367a10 100755 --- a/scripts/ci-checks.sh +++ b/scripts/ci-checks.sh @@ -69,7 +69,7 @@ crates_with_v1_feature="$( --null-input \ '$crates_with_features[] | select( IN("v1"; .features[])) # Select crates with `v1` feature - | { name, features: (.features - ["v1", "v2", "default", "payment_v2", "merchant_account_v2","customer_v2"]) } # Remove specific features to generate feature combinations + | { name, features: (.features - ["v1", "v2", "default", "payment_v2", "merchant_account_v2","customer_v2", "payment_methods_v2"]) } # Remove specific features to generate feature combinations | { name, features: ( .features | map([., "v1"] | join(",")) ) } # Add `v1` to remaining features and join them by comma | .name as $name | .features[] | { $name, features: . } # Expand nested features object to have package - features combinations | "\(.name) \(.features)" # Print out package name and features separated by space' From 75486a15e239d2b89ba9b2f04664155ed5a19f20 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:18:53 +0000 Subject: [PATCH 24/30] chore: run formatter --- crates/api_models/src/events/payment.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 177f81cac69..589dcd8d10b 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -1,4 +1,5 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; + #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -8,11 +9,11 @@ use crate::payment_methods::CustomerPaymentMethodsListResponse; use crate::payment_methods::CustomerPaymentMethodsListResponse; use crate::{ payment_methods::{ - CustomerDefaultPaymentMethodResponse, - DefaultPaymentMethod, ListCountriesCurrenciesRequest, ListCountriesCurrenciesResponse, - PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, - PaymentMethodCollectLinkResponse, PaymentMethodDeleteResponse, PaymentMethodListRequest, - PaymentMethodListResponse, PaymentMethodResponse, PaymentMethodUpdate, + CustomerDefaultPaymentMethodResponse, DefaultPaymentMethod, ListCountriesCurrenciesRequest, + ListCountriesCurrenciesResponse, PaymentMethodCollectLinkRenderRequest, + PaymentMethodCollectLinkRequest, PaymentMethodCollectLinkResponse, + PaymentMethodDeleteResponse, PaymentMethodListRequest, PaymentMethodListResponse, + PaymentMethodResponse, PaymentMethodUpdate, }, payments::{ ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, From 1efda3c8a74eb8abcf599076957723d93c4c8c65 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 23 Jul 2024 18:11:55 +0530 Subject: [PATCH 25/30] fix(payment_methods): Fixed errors --- crates/router/src/core/payment_methods/cards.rs | 2 -- crates/router/src/routes/app.rs | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 24fafa616cf..c433da672d1 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -48,8 +48,6 @@ use super::surcharge_decision_configs::{ perform_surcharge_decision_management_for_payment_method_list, perform_surcharge_decision_management_for_saved_cards, }; -#[cfg(not(feature = "connector_choice_mca_id"))] -use crate::core::utils::get_connector_label; #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index abe33dec0bb..090bc0d3c17 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -43,8 +43,7 @@ use super::{ admin::*, api_keys::*, apple_pay_certificates_migration, connector_onboarding::*, disputes::*, files::*, gsm::*, payment_link::*, user::*, user_role::*, webhook_events::*, }; -use super::{cache::*, configs::*, customers::*, health::*, mandates::*, payments::*, refunds::*}; -// use super::{configs::*, mandates::*, payments::*, refunds::*}; +use super::{cache::*, health::*}; #[cfg(any(feature = "olap", feature = "oltp"))] use super::{currency, payment_methods::*}; #[cfg(feature = "oltp")] From 2049014b0cd13737385693ebce32edb9b34cacf4 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 23 Jul 2024 19:38:41 +0530 Subject: [PATCH 26/30] fix(payment_methods): Fixed errors --- crates/router/src/routes/app.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 090bc0d3c17..dd791ab6450 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -45,6 +45,8 @@ use super::{ }; use super::{cache::*, health::*}; #[cfg(any(feature = "olap", feature = "oltp"))] +use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; +#[cfg(any(feature = "olap", feature = "oltp"))] use super::{currency, payment_methods::*}; #[cfg(feature = "oltp")] use super::{ephemeral_key::*, webhooks::*}; @@ -849,7 +851,7 @@ impl Customers { ))] impl Customers { pub fn server(state: AppState) -> Scope { - let mut route = web::scope("customers").app_data(web::Data::new(state)); + let mut route = web::scope("/customers").app_data(web::Data::new(state)); #[cfg(feature = "olap")] { From 137091bdb61d710aa85ffbba846ec0f5d059b6a5 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 25 Jul 2024 15:36:43 +0530 Subject: [PATCH 27/30] fix(payment_methods): Resolved comments --- .../compatibility/stripe/customers/types.rs | 14 ++- .../surcharge_decision_configs.rs | 106 ++++++++++++++++-- 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 1fbe321b1e1..1dc43175342 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -225,17 +225,23 @@ impl From for CustomerPaymentMethodList // Check this in review impl From for PaymentMethodData { fn from(item: api_types::CustomerPaymentMethod) -> Self { - #[cfg(not(feature = "v2"))] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] let card = item.card.map(From::from); - #[cfg(feature = "v2")] + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] let card = match item.payment_method_data { Some(api_types::PaymentMethodListData::Card(card)) => Some(CardDetails::from(card)), _ => None, }; Self { - #[cfg(not(feature = "v2"))] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] id: Some(item.payment_token), - #[cfg(feature = "v2")] + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] id: item.payment_token, object: "payment_method", card, diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index ff717e8a5f5..3cd2e967b31 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -3,9 +3,12 @@ use api_models::{ payments, routing, surcharge_decision_configs::{self, SurchargeDecisionConfigs, SurchargeDecisionManagerRecord}, }; -#[cfg(not(feature = "v2"))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] use common_utils::{ext_traits::StringExt, types as common_utils_types}; -#[cfg(feature = "v2")] +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use common_utils::{ ext_traits::{OptionExt, StringExt}, types as common_utils_types, @@ -273,6 +276,11 @@ where } Ok(surcharge_metadata) } + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn perform_surcharge_decision_management_for_saved_cards( state: &SessionState, algorithm_ref: routing::RoutingAlgorithmRef, @@ -309,21 +317,12 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( .change_context(ConfigError::InputConstructionError)?; for customer_payment_method in customer_payment_method_list.iter_mut() { - #[cfg(not(feature = "v2"))] let payment_token = customer_payment_method.payment_token.clone(); - #[cfg(feature = "v2")] - let payment_token = customer_payment_method - .payment_token - .clone() - .get_required_value("payment_token") - .change_context(ConfigError::InputConstructionError)?; - backend_input.payment_method.payment_method = Some(customer_payment_method.payment_method); backend_input.payment_method.payment_method_type = customer_payment_method.payment_method_type; - #[cfg(not(feature = "v2"))] let card_network = customer_payment_method .card .as_ref() @@ -335,7 +334,90 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( .change_context(ConfigError::DslExecutionError) }) .transpose()?; - #[cfg(feature = "v2")] + + let card_network = match &customer_payment_method.payment_method_data { + Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => card + .scheme + .as_ref() + .map(|scheme| { + scheme + .clone() + .parse_enum("CardNetwork") + .change_context(ConfigError::DslExecutionError) + }) + .transpose()?, + _ => None, + }; + + backend_input.payment_method.card_network = card_network; + + let surcharge_details = surcharge_source + .generate_surcharge_details_and_populate_surcharge_metadata( + &backend_input, + payment_attempt, + ( + &mut surcharge_metadata, + types::SurchargeKey::Token(payment_token), + ), + )?; + customer_payment_method.surcharge_details = surcharge_details + .map(|surcharge_details| { + SurchargeDetailsResponse::foreign_try_from((&surcharge_details, payment_attempt)) + .change_context(ConfigError::DslParsingError) + }) + .transpose()?; + } + Ok(surcharge_metadata) +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn perform_surcharge_decision_management_for_saved_cards( + state: &SessionState, + algorithm_ref: routing::RoutingAlgorithmRef, + payment_attempt: &storage::PaymentAttempt, + payment_intent: &storage::PaymentIntent, + customer_payment_method_list: &mut [api_models::payment_methods::CustomerPaymentMethod], +) -> ConditionalConfigResult { + let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.attempt_id.clone()); + let surcharge_source = match ( + payment_attempt.get_surcharge_details(), + algorithm_ref.surcharge_config_algo_id, + ) { + (Some(request_surcharge_details), _) => { + SurchargeSource::Predetermined(request_surcharge_details) + } + (None, Some(algorithm_id)) => { + let cached_algo = ensure_algorithm_cached( + &*state.store, + &payment_attempt.merchant_id, + algorithm_id.as_str(), + ) + .await?; + + SurchargeSource::Generate(cached_algo) + } + (None, None) => return Ok(surcharge_metadata), + }; + let surcharge_source_log_message = match &surcharge_source { + SurchargeSource::Generate(_) => "Surcharge was calculated through surcharge rules", + SurchargeSource::Predetermined(_) => "Surcharge was sent in payment create request", + }; + logger::debug!(customer_saved_card_list_surcharge_source = surcharge_source_log_message); + let mut backend_input = make_dsl_input_for_surcharge(payment_attempt, payment_intent, None) + .change_context(ConfigError::InputConstructionError)?; + + for customer_payment_method in customer_payment_method_list.iter_mut() { + + let payment_token = customer_payment_method + .payment_token + .clone() + .get_required_value("payment_token") + .change_context(ConfigError::InputConstructionError)?; + + backend_input.payment_method.payment_method = Some(customer_payment_method.payment_method); + backend_input.payment_method.payment_method_type = + customer_payment_method.payment_method_type; + let card_network = match &customer_payment_method.payment_method_data { Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => card .scheme From 185bc62c0b386f2fc7e69187fe37fcc038e5e99c Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:11:37 +0000 Subject: [PATCH 28/30] chore: run formatter --- .../src/core/payment_methods/surcharge_decision_configs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 6d93f16ed68..d5cc18f751f 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -407,7 +407,6 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( .change_context(ConfigError::InputConstructionError)?; for customer_payment_method in customer_payment_method_list.iter_mut() { - let payment_token = customer_payment_method .payment_token .clone() From 69a2b80b23c798a50096ce5a6b42e2b5a463bc3d Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 25 Jul 2024 16:04:53 +0530 Subject: [PATCH 29/30] fix(payment_methods): Fixed errors --- .../payment_methods/surcharge_decision_configs.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index d5cc18f751f..23f79848dee 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -335,20 +335,6 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( }) .transpose()?; - let card_network = match &customer_payment_method.payment_method_data { - Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => card - .scheme - .as_ref() - .map(|scheme| { - scheme - .clone() - .parse_enum("CardNetwork") - .change_context(ConfigError::DslExecutionError) - }) - .transpose()?, - _ => None, - }; - backend_input.payment_method.card_network = card_network; let surcharge_details = surcharge_source From 5328c9f236b449b8589fd796794c61d07cd5a196 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Sat, 27 Jul 2024 22:45:02 +0530 Subject: [PATCH 30/30] fix(payment_methods): Fixed errors --- .../router/src/core/payment_methods/cards.rs | 36 ++++++++++--------- crates/router/src/routes/app.rs | 4 +-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index d22a93c5445..3bfa70f8e69 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4137,7 +4137,7 @@ impl SavedPMLPaymentsInfo { ) -> errors::RouterResult { let requires_cvv = db .find_config_by_key_unwrap_or( - format!("{}_requires_cvv", merchant_account.merchant_id).as_str(), + format!("{}_requires_cvv", merchant_account.get_id()).as_str(), Some("true".to_string()), ) .await @@ -4154,7 +4154,7 @@ impl SavedPMLPaymentsInfo { let profile_id = core_utils::get_profile_id_from_business_details( payment_intent.business_country, payment_intent.business_label.as_ref(), - &merchant_account, + merchant_account, payment_intent.profile_id.as_ref(), db, false, @@ -4165,7 +4165,7 @@ impl SavedPMLPaymentsInfo { let business_profile = core_utils::validate_and_get_business_profile( db, Some(profile_id).as_ref(), - &merchant_account.merchant_id, + merchant_account.get_id(), ) .await?; @@ -4258,7 +4258,7 @@ pub async fn list_customer_payment_method( .find_customer_by_customer_id_merchant_id( key_manager_state, customer_id, - &merchant_account.merchant_id, + merchant_account.get_id(), &key_store, merchant_account.storage_scheme, ) @@ -4273,7 +4273,7 @@ pub async fn list_customer_payment_method( let saved_payment_methods = db .find_payment_method_by_customer_id_merchant_id_status( customer_id, - &merchant_account.merchant_id, + merchant_account.get_id(), common_enums::PaymentMethodStatus::Active, limit, merchant_account.storage_scheme, @@ -4326,7 +4326,7 @@ pub async fn list_customer_payment_method( let mut response = api::CustomerPaymentMethodsListResponse { customer_payment_methods: customer_pms, - is_guest_customer: Some(is_payment_associated), //to return this key only when the request is tied to a payment intent + is_guest_customer: is_payment_associated.then_some(false), //to return this key only when the request is tied to a payment intent }; if is_payment_associated { @@ -4400,7 +4400,7 @@ async fn generate_saved_pm_response( let mca_enabled = get_mca_status( state, key_store, - &merchant_account.merchant_id, + merchant_account.get_id(), is_connector_agnostic_mit_enabled, connector_mandate_details, pm.network_transaction_id.as_ref(), @@ -4414,16 +4414,18 @@ async fn generate_saved_pm_response( } else { requires_cvv && !(off_session_payment_flag && pm.connector_mandate_details.is_some()) }; - #[cfg(not(feature = "payouts"))] - let pmd = pm_list_context - .card_details - .clone() - .map(api::PaymentMethodListData::Card); - #[cfg(feature = "payouts")] - let pmd = pm_list_context - .bank_transfer_details - .clone() - .map(api::PaymentMethodListData::Bank); + + let pmd = if let Some(card) = pm_list_context.card_details.as_ref() { + Some(api::PaymentMethodListData::Card(card.clone())) + } else if cfg!(feature = "payouts") { + pm_list_context + .bank_transfer_details + .clone() + .map(api::PaymentMethodListData::Bank) + } else { + None + }; + let pma = api::CustomerPaymentMethod { payment_token: parent_payment_method_token.clone(), payment_method_id: pm.payment_method_id.clone(), diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 2ab2e746795..3d1ad81c027 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -24,7 +24,7 @@ use super::blocklist; use super::currency; #[cfg(feature = "dummy_connector")] use super::dummy_connector::*; -#[cfg(all(any(feature = "olap", feature = "oltp"), not(feature = "customer_v2")))] +#[cfg(any(feature = "olap", feature = "oltp"))] use super::payment_methods::*; #[cfg(feature = "payouts")] use super::payout_link::*; @@ -461,7 +461,7 @@ pub struct Payments; ))] impl Payments { pub fn server(state: AppState) -> Scope { - let mut route = web::scope("v2/payments").app_data(web::Data::new(state)); + let mut route = web::scope("/v2/payments").app_data(web::Data::new(state)); route = route.service( web::resource("/{payment_id}/saved_payment_methods") .route(web::get().to(list_customer_payment_method_for_payment)),