mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 09:38:33 +08:00
feat: Filter payment methods based on payment (#331)
This commit is contained in:
@ -71,7 +71,7 @@ pub struct ListPaymentMethodRequest {
|
||||
pub client_secret: Option<String>,
|
||||
pub accepted_countries: Option<Vec<String>>,
|
||||
pub accepted_currencies: Option<Vec<api_enums::Currency>>,
|
||||
pub amount: Option<i32>,
|
||||
pub amount: Option<i64>,
|
||||
pub recurring_enabled: Option<bool>,
|
||||
pub installment_payment_enabled: Option<bool>,
|
||||
}
|
||||
@ -174,8 +174,8 @@ pub struct ListPaymentMethodResponse {
|
||||
pub payment_schemes: Option<Vec<String>>,
|
||||
pub accepted_countries: Option<Vec<String>>,
|
||||
pub accepted_currencies: Option<Vec<api_enums::Currency>>,
|
||||
pub minimum_amount: Option<i32>,
|
||||
pub maximum_amount: Option<i32>,
|
||||
pub minimum_amount: Option<i64>,
|
||||
pub maximum_amount: Option<i64>,
|
||||
pub recurring_enabled: bool,
|
||||
pub installment_payment_enabled: bool,
|
||||
pub payment_experience: Option<Vec<PaymentExperience>>,
|
||||
|
||||
@ -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<Vec<api::ListPaymentMethodResponse>> {
|
||||
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<serde_json::Value>,
|
||||
req: &mut api::ListPaymentMethodRequest,
|
||||
resp: &mut Vec<api::ListPaymentMethodResponse>,
|
||||
) {
|
||||
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::<api::ListPaymentMethodResponse>(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<T: Eq + std::hash::Hash + Clone>(
|
||||
@ -449,7 +493,7 @@ fn filter_accepted_enum_based<T: Eq + std::hash::Hash + Clone>(
|
||||
|
||||
fn filter_amount_based(
|
||||
payment_method: &api::ListPaymentMethodResponse,
|
||||
amount: Option<i32>,
|
||||
amount: Option<i64>,
|
||||
) -> 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<bool, errors::ApiErrorResponse> {
|
||||
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<bool, errors::ApiErrorResponse> {
|
||||
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,
|
||||
|
||||
@ -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<String>,
|
||||
merchant_id: &str,
|
||||
) -> error_stack::Result<(), errors::ApiErrorResponse> {
|
||||
match client_secret {
|
||||
None => Ok(()),
|
||||
Some(cs) => {
|
||||
let payment_id = cs.split('_').take(2).collect::<Vec<&str>>().join("_");
|
||||
) -> error_stack::Result<Option<storage::PaymentIntent>, 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::<Vec<&str>>().join("_")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -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}")
|
||||
|
||||
@ -32,7 +32,6 @@ pub async fn create_payment_method_api(
|
||||
pub async fn list_payment_method_api(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
_merchant_id: web::Path<String>,
|
||||
json_payload: web::Query<payment_methods::ListPaymentMethodRequest>,
|
||||
) -> HttpResponse {
|
||||
let payload = json_payload.into_inner();
|
||||
|
||||
@ -279,6 +279,11 @@ impl From<F<api_enums::Currency>> for F<storage_enums::Currency> {
|
||||
Self(frunk::labelled_convert_from(currency.0))
|
||||
}
|
||||
}
|
||||
impl From<F<storage_enums::Currency>> for F<api_enums::Currency> {
|
||||
fn from(currency: F<storage_enums::Currency>) -> Self {
|
||||
Self(frunk::labelled_convert_from(currency.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<F<&'a api_types::Address>> for F<storage::AddressUpdate> {
|
||||
fn from(address: F<&api_types::Address>) -> Self {
|
||||
|
||||
Reference in New Issue
Block a user