diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 6556f043f1..96b5bc02d0 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -71,7 +71,7 @@ pub struct ListPaymentMethodRequest { pub client_secret: Option, pub accepted_countries: Option>, pub accepted_currencies: Option>, - pub amount: Option, + pub amount: Option, pub recurring_enabled: Option, pub installment_payment_enabled: Option, } @@ -174,8 +174,8 @@ pub struct ListPaymentMethodResponse { pub payment_schemes: Option>, pub accepted_countries: Option>, pub accepted_currencies: Option>, - pub minimum_amount: Option, - pub maximum_amount: Option, + pub minimum_amount: Option, + pub maximum_amount: Option, pub recurring_enabled: bool, pub installment_payment_enabled: bool, pub payment_experience: Option>, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 06ba52964d..a4d3b1cdfd 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1,6 +1,6 @@ use std::collections; -use common_utils::{consts, generate_id}; +use common_utils::{consts, ext_traits::AsyncExt, generate_id}; use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; @@ -355,13 +355,35 @@ pub async fn list_payment_methods( merchant_account: storage::MerchantAccount, mut req: api::ListPaymentMethodRequest, ) -> errors::RouterResponse> { - helpers::verify_client_secret( + let payment_intent = helpers::verify_client_secret( db, merchant_account.storage_scheme, req.client_secret.clone(), &merchant_account.merchant_id, ) .await?; + let address = payment_intent + .as_ref() + .async_map(|pi| async { + helpers::get_address_by_id(db, pi.billing_address_id.clone()).await + }) + .await + .transpose()? + .flatten(); + + let payment_attempt = payment_intent + .as_ref() + .async_map(|pi| async { + db.find_payment_attempt_by_payment_id_merchant_id( + &pi.payment_id, + &pi.merchant_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentNotFound) + }) + .await + .transpose()?; let all_mcas = db .find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id) @@ -378,19 +400,31 @@ pub async fn list_payment_methods( None => continue, }; - filter_payment_methods(payment_methods, &mut req, &mut response); + filter_payment_methods( + payment_methods, + &mut req, + &mut response, + payment_intent.as_ref(), + payment_attempt.as_ref(), + address.as_ref(), + ) + .await?; } + response .is_empty() .then(|| Err(report!(errors::ApiErrorResponse::PaymentMethodNotFound))) .unwrap_or(Ok(services::ApplicationResponse::Json(response))) } -fn filter_payment_methods( +async fn filter_payment_methods( payment_methods: Vec, req: &mut api::ListPaymentMethodRequest, resp: &mut Vec, -) { + payment_intent: Option<&storage::PaymentIntent>, + payment_attempt: Option<&storage::PaymentAttempt>, + address: Option<&storage::Address>, +) -> errors::CustomResult<(), errors::ApiErrorResponse> { for payment_method in payment_methods.into_iter() { if let Ok(payment_method_object) = serde_json::from_value::(payment_method) @@ -420,13 +454,23 @@ fn filter_payment_methods( &payment_method_object.accepted_currencies, &req.accepted_currencies, ); + let filter3 = if let Some(payment_intent) = payment_intent { + filter_payment_country_based(&payment_method_object, address).await? + && filter_payment_currency_based(payment_intent, &payment_method_object) + && filter_payment_amount_based(payment_intent, &payment_method_object) + && filter_payment_mandate_based(payment_attempt, &payment_method_object) + .await? + } else { + true + }; - if filter && filter2 { + if filter && filter2 && filter3 { resp.push(payment_method_object); } } } } + Ok(()) } fn filter_accepted_enum_based( @@ -449,7 +493,7 @@ fn filter_accepted_enum_based( fn filter_amount_based( payment_method: &api::ListPaymentMethodResponse, - amount: Option, + amount: Option, ) -> bool { let min_check = amount .and_then(|amt| payment_method.minimum_amount.map(|min_amt| amt >= min_amt)) @@ -484,6 +528,51 @@ fn filter_installment_based( }) } +async fn filter_payment_country_based( + pm: &api::ListPaymentMethodResponse, + address: Option<&storage::Address>, +) -> errors::CustomResult { + Ok(address.map_or(true, |address| { + address.country.as_ref().map_or(true, |country| { + pm.accepted_countries + .clone() + .map_or(true, |ac| ac.contains(country)) + }) + })) +} + +fn filter_payment_currency_based( + payment_intent: &storage::PaymentIntent, + pm: &api::ListPaymentMethodResponse, +) -> bool { + payment_intent.currency.map_or(true, |currency| { + pm.accepted_currencies + .clone() + .map_or(true, |ac| ac.contains(¤cy.foreign_into())) + }) +} + +fn filter_payment_amount_based( + payment_intent: &storage::PaymentIntent, + pm: &api::ListPaymentMethodResponse, +) -> bool { + let amount = payment_intent.amount; + pm.maximum_amount.map_or(true, |amt| amount < amt) + && pm.minimum_amount.map_or(true, |amt| amount > amt) +} + +async fn filter_payment_mandate_based( + payment_attempt: Option<&storage::PaymentAttempt>, + pm: &api::ListPaymentMethodResponse, +) -> errors::CustomResult { + let recurring_filter = if !pm.recurring_enabled { + payment_attempt.map_or(true, |pa| pa.mandate_id.is_none()) + } else { + true + }; + Ok(recurring_filter) +} + pub async fn list_customer_payment_method( state: &routes::AppState, merchant_account: storage::MerchantAccount, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 0b8f13656f..d1f25cb1f5 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +use common_utils::ext_traits::AsyncExt; // TODO : Evaluate all the helper functions () use error_stack::{report, IntoReport, ResultExt}; use masking::{ExposeOptionInterface, PeekInterface}; @@ -1353,11 +1354,10 @@ pub(crate) async fn verify_client_secret( storage_scheme: storage_enums::MerchantStorageScheme, client_secret: Option, merchant_id: &str, -) -> error_stack::Result<(), errors::ApiErrorResponse> { - match client_secret { - None => Ok(()), - Some(cs) => { - let payment_id = cs.split('_').take(2).collect::>().join("_"); +) -> error_stack::Result, errors::ApiErrorResponse> { + client_secret + .async_map(|cs| async move { + let payment_id = get_payment_id_from_client_secret(&cs); let payment_intent = db .find_payment_intent_by_payment_id_merchant_id( @@ -1369,9 +1369,16 @@ pub(crate) async fn verify_client_secret( .change_context(errors::ApiErrorResponse::PaymentNotFound)?; authenticate_client_secret(Some(&cs), payment_intent.client_secret.as_ref()) - .map_err(|err| err.into()) - } - } + .map_err(errors::ApiErrorResponse::from)?; + Ok(payment_intent) + }) + .await + .transpose() +} + +#[inline] +pub(crate) fn get_payment_id_from_client_secret(cs: &str) -> String { + cs.split('_').take(2).collect::>().join("_") } #[cfg(test)] diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index f7b7d868ff..360989662f 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -181,8 +181,7 @@ impl MerchantConnectorAccount { .route(web::get().to(payment_connector_list)), ) .service( - web::resource("/{merchant_id}/payment_methods") - .route(web::get().to(list_payment_method_api)), + web::resource("/payment_methods").route(web::get().to(list_payment_method_api)), ) .service( web::resource("/{merchant_id}/connectors/{merchant_connector_id}") diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 93da254a9e..d21d584c1e 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -32,7 +32,6 @@ pub async fn create_payment_method_api( pub async fn list_payment_method_api( state: web::Data, req: HttpRequest, - _merchant_id: web::Path, json_payload: web::Query, ) -> HttpResponse { let payload = json_payload.into_inner(); diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 4df1897b99..b05984bdf6 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -279,6 +279,11 @@ impl From> for F { Self(frunk::labelled_convert_from(currency.0)) } } +impl From> for F { + fn from(currency: F) -> Self { + Self(frunk::labelled_convert_from(currency.0)) + } +} impl<'a> From> for F { fn from(address: F<&api_types::Address>) -> Self {