refactor(pm_list): modify pm list to support new api contract (#657)

Co-authored-by: Sangamesh <sangamesh.kulkarni@juspay.in>
This commit is contained in:
Narayan Bhat
2023-02-27 00:51:29 +05:30
committed by GitHub
parent d1c9305ebb
commit a2616d87b1
12 changed files with 422 additions and 252 deletions

View File

@ -260,7 +260,7 @@ pub async fn create_payment_connector(
let payment_methods_enabled = match req.payment_methods_enabled {
Some(val) => {
for pm in val.into_iter() {
let pm_value = utils::Encode::<api::PaymentMethods>::encode_to_value(&pm)
let pm_value = utils::Encode::<api::PaymentMethodsEnabled>::encode_to_value(&pm)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Failed while encoding to serde_json::Value, PaymentMethod",
@ -345,7 +345,7 @@ pub async fn list_payment_connectors(
})?;
let merchant_connector_accounts = store
.find_merchant_connector_account_by_merchant_id_list(&merchant_id)
.find_merchant_connector_account_by_merchant_id_and_disabled_list(&merchant_id, true)
.await
.map_err(|error| {
error.to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound)
@ -387,7 +387,7 @@ pub async fn update_payment_connector(
pm_enabled
.iter()
.flat_map(|payment_method| {
utils::Encode::<api::PaymentMethods>::encode_to_value(payment_method)
utils::Encode::<api::PaymentMethodsEnabled>::encode_to_value(payment_method)
})
.collect::<Vec<serde_json::Value>>()
});
@ -415,12 +415,12 @@ pub async fn update_payment_connector(
let updated_pm_enabled = updated_mca.payment_methods_enabled.map(|pm| {
pm.into_iter()
.flat_map(|pm_value| {
ValueExt::<api_models::admin::PaymentMethods>::parse_value(
ValueExt::<api_models::admin::PaymentMethodsEnabled>::parse_value(
pm_value,
"PaymentMethods",
)
})
.collect::<Vec<api_models::admin::PaymentMethods>>()
.collect::<Vec<api_models::admin::PaymentMethodsEnabled>>()
});
let response = api::PaymentConnectorCreate {

View File

@ -1,6 +1,14 @@
use std::collections::{HashMap, HashSet};
use api_models::{admin, enums as api_enums};
use api_models::{
admin::{self, PaymentMethodsEnabled},
enums as api_enums,
payment_methods::{
CardNetworkTypes, PaymentExperienceTypes, RequestPaymentMethodTypes,
ResponsePaymentMethodIntermediate, ResponsePaymentMethodTypes,
ResponsePaymentMethodsEnabled,
},
};
use common_utils::{consts, ext_traits::AsyncExt, generate_id};
use error_stack::{report, ResultExt};
use router_env::{instrument, tracing};
@ -79,11 +87,9 @@ pub async fn add_payment_method(
payment_method_id: payment_method_id.to_string(),
payment_method: req.payment_method,
payment_method_type: req.payment_method_type,
payment_method_issuer: req.payment_method_issuer,
card: None,
metadata: req.metadata,
created: Some(common_utils::date_time::now()),
payment_method_issuer_code: req.payment_method_issuer_code,
recurring_enabled: false, //[#219]
installment_payment_enabled: false, //[#219]
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219]
@ -150,6 +156,7 @@ pub async fn add_card(
&locker_id,
merchant_id,
)?;
let response = if !locker.mock_locker {
let response = services::call_connector_api(state, request)
.await
@ -355,7 +362,7 @@ pub async fn list_payment_methods(
let address = payment_intent
.as_ref()
.async_map(|pi| async {
helpers::get_address_by_id(db, pi.billing_address_id.clone()).await
helpers::get_address_by_id(db, pi.shipping_address_id.clone()).await
})
.await
.transpose()?
@ -376,13 +383,16 @@ pub async fn list_payment_methods(
.transpose()?;
let all_mcas = db
.find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id)
.find_merchant_connector_account_by_merchant_id_and_disabled_list(
&merchant_account.merchant_id,
false,
)
.await
.map_err(|error| {
error.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
})?;
let mut response: HashMap<api::ListPaymentMethod, Vec<String>> = HashMap::new();
let mut response: Vec<ResponsePaymentMethodIntermediate> = vec![];
for mca in all_mcas {
let payment_methods = match mca.payment_methods_enabled {
Some(pm) => pm,
@ -401,19 +411,125 @@ pub async fn list_payment_methods(
.await?;
}
let mut payment_experiences_consolidated_hm: HashMap<
api_enums::PaymentMethod,
HashMap<api_enums::PaymentMethodType, HashMap<api_enums::PaymentExperience, Vec<String>>>,
> = HashMap::new();
let mut card_networks_consolidated_hm: HashMap<
api_enums::PaymentMethod,
HashMap<api_enums::PaymentMethodType, HashMap<api_enums::CardNetwork, Vec<String>>>,
> = HashMap::new();
for element in response.clone() {
let payment_method = element.payment_method;
let payment_method_type = element.payment_method_type;
let connector = element.connector.clone();
if let Some(payment_experience) = element.payment_experience {
if let Some(payment_method_hm) =
payment_experiences_consolidated_hm.get_mut(&payment_method)
{
if let Some(payment_method_type_hm) =
payment_method_hm.get_mut(&payment_method_type)
{
if let Some(vector_of_connectors) =
payment_method_type_hm.get_mut(&payment_experience)
{
vector_of_connectors.push(connector);
} else {
payment_method_type_hm.insert(payment_experience, vec![connector]);
}
} else {
payment_method_hm.insert(payment_method_type, HashMap::new());
}
} else {
payment_experiences_consolidated_hm.insert(payment_method, HashMap::new());
}
}
if let Some(card_networks) = element.card_networks {
if let Some(payment_method_hm) = card_networks_consolidated_hm.get_mut(&payment_method)
{
if let Some(payment_method_type_hm) =
payment_method_hm.get_mut(&payment_method_type)
{
for card_network in card_networks {
if let Some(vector_of_connectors) =
payment_method_type_hm.get_mut(&card_network)
{
let connector = element.connector.clone(); //FIXME: remove clone
vector_of_connectors.push(connector);
} else {
let connector = element.connector.clone(); //FIXME: remove clone
payment_method_type_hm.insert(card_network, vec![connector]);
}
}
} else {
payment_method_hm.insert(payment_method_type, HashMap::new());
}
} else {
card_networks_consolidated_hm.insert(payment_method, HashMap::new());
}
}
}
let mut payment_method_responses: Vec<ResponsePaymentMethodsEnabled> = vec![];
for key in payment_experiences_consolidated_hm.iter() {
let mut payment_method_types = vec![];
for payment_method_types_hm in key.1 {
let mut payment_experience_types = vec![];
for payment_experience_type in payment_method_types_hm.1 {
payment_experience_types.push(PaymentExperienceTypes {
payment_experience_type: *payment_experience_type.0,
eligible_connectors: payment_experience_type.1.clone(),
})
}
payment_method_types.push(ResponsePaymentMethodTypes {
payment_method_type: *payment_method_types_hm.0,
payment_experience: Some(payment_experience_types),
card_networks: None,
})
}
payment_method_responses.push(ResponsePaymentMethodsEnabled {
payment_method: *key.0,
payment_method_types,
})
}
for key in card_networks_consolidated_hm.iter() {
let mut payment_method_types = vec![];
for payment_method_types_hm in key.1 {
let mut card_network_types = vec![];
for card_network_type in payment_method_types_hm.1 {
card_network_types.push(CardNetworkTypes {
card_network: card_network_type.0.clone(),
eligible_connectors: card_network_type.1.clone(),
})
}
payment_method_types.push(ResponsePaymentMethodTypes {
payment_method_type: *payment_method_types_hm.0,
card_networks: Some(card_network_types),
payment_experience: None,
})
}
payment_method_responses.push(ResponsePaymentMethodsEnabled {
payment_method: *key.0,
payment_method_types,
})
}
response
.is_empty()
.then(|| Err(report!(errors::ApiErrorResponse::PaymentMethodNotFound)))
.unwrap_or(Ok(services::ApplicationResponse::Json(
api::ListPaymentMethodResponse {
redirect_url: merchant_account.return_url,
payment_methods: response
.into_iter()
.map(|(mut key, val)| {
key.eligible_connectors = Some(val);
key
})
.collect(),
payment_methods: payment_method_responses,
},
)))
}
@ -421,54 +537,74 @@ pub async fn list_payment_methods(
async fn filter_payment_methods(
payment_methods: Vec<serde_json::Value>,
req: &mut api::ListPaymentMethodRequest,
resp: &mut HashMap<api::ListPaymentMethod, Vec<String>>,
resp: &mut Vec<ResponsePaymentMethodIntermediate>,
payment_intent: Option<&storage::PaymentIntent>,
payment_attempt: Option<&storage::PaymentAttempt>,
address: Option<&storage::Address>,
connector_name: String,
connector: String,
) -> errors::CustomResult<(), errors::ApiErrorResponse> {
for payment_method in payment_methods.into_iter() {
if let Ok(payment_method_object) =
serde_json::from_value::<api::ListPaymentMethod>(payment_method)
{
if filter_recurring_based(&payment_method_object, req.recurring_enabled)
&& filter_installment_based(&payment_method_object, req.installment_payment_enabled)
&& filter_amount_based(&payment_method_object, req.amount)
let parse_result = serde_json::from_value::<PaymentMethodsEnabled>(payment_method);
if let Ok(payment_methods_enabled) = parse_result {
let payment_method = payment_methods_enabled.payment_method;
for payment_method_type_info in payment_methods_enabled
.payment_method_types
.unwrap_or_default()
{
let mut payment_method_object = payment_method_object;
if filter_recurring_based(&payment_method_type_info, req.recurring_enabled)
&& filter_installment_based(
&payment_method_type_info,
req.installment_payment_enabled,
)
&& filter_amount_based(&payment_method_type_info, req.amount)
{
let mut payment_method_object = payment_method_type_info;
let filter;
(
payment_method_object.accepted_countries,
req.accepted_countries,
filter,
) = filter_pm_country_based(
&payment_method_object.accepted_countries,
&req.accepted_countries,
);
let filter2;
(
payment_method_object.accepted_currencies,
req.accepted_currencies,
filter2,
) = filter_pm_currencies_based(
&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
};
let filter;
(
payment_method_object.accepted_countries,
req.accepted_countries,
filter,
) = filter_pm_country_based(
&payment_method_object.accepted_countries,
&req.accepted_countries,
);
let filter2;
(
payment_method_object.accepted_currencies,
req.accepted_currencies,
filter2,
) = filter_pm_currencies_based(
&payment_method_object.accepted_currencies,
&req.accepted_currencies,
);
if filter && filter2 && filter3 {
resp.entry(payment_method_object)
.or_insert_with(Vec::new)
.push(connector_name.clone());
let filter4 = filter_pm_card_network_based(
payment_method_object.card_networks.as_ref(),
req.card_networks.as_ref(),
);
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
};
let connector = connector.clone();
let response_pm_type = ResponsePaymentMethodIntermediate::new(
payment_method_object,
connector,
payment_method,
);
if filter && filter2 && filter3 && filter4 {
resp.push(response_pm_type);
}
}
}
}
@ -476,6 +612,18 @@ async fn filter_payment_methods(
Ok(())
}
fn filter_pm_card_network_based(
pm_card_networks: Option<&Vec<api_enums::CardNetwork>>,
request_card_networks: Option<&Vec<api_enums::CardNetwork>>,
) -> bool {
match (pm_card_networks, request_card_networks) {
(Some(pm_card_networks), Some(request_card_networks)) => pm_card_networks
.iter()
.all(|card_network| request_card_networks.contains(card_network)),
(None, Some(_)) => false,
_ => true,
}
}
fn filter_pm_country_based(
accepted_countries: &Option<admin::AcceptedCountries>,
req_country_list: &Option<Vec<String>>,
@ -582,12 +730,20 @@ fn filter_disabled_enum_based<T: Eq + std::hash::Hash + Clone>(
}
}
fn filter_amount_based(payment_method: &api::ListPaymentMethod, amount: Option<i64>) -> bool {
fn filter_amount_based(payment_method: &RequestPaymentMethodTypes, amount: Option<i64>) -> bool {
let min_check = amount
.and_then(|amt| payment_method.minimum_amount.map(|min_amt| amt >= min_amt))
.and_then(|amt| {
payment_method
.minimum_amount
.map(|min_amt| amt >= min_amt.into())
})
.unwrap_or(true);
let max_check = amount
.and_then(|amt| payment_method.maximum_amount.map(|max_amt| amt <= max_amt))
.and_then(|amt| {
payment_method
.maximum_amount
.map(|max_amt| amt <= max_amt.into())
})
.unwrap_or(true);
// let min_check = match (amount, payment_method.minimum_amount) {
// (Some(amt), Some(min_amt)) => amt >= min_amt,
@ -601,14 +757,14 @@ fn filter_amount_based(payment_method: &api::ListPaymentMethod, amount: Option<i
}
fn filter_recurring_based(
payment_method: &api::ListPaymentMethod,
payment_method: &RequestPaymentMethodTypes,
recurring_enabled: Option<bool>,
) -> bool {
recurring_enabled.map_or(true, |enabled| payment_method.recurring_enabled == enabled)
}
fn filter_installment_based(
payment_method: &api::ListPaymentMethod,
payment_method: &RequestPaymentMethodTypes,
installment_payment_enabled: Option<bool>,
) -> bool {
installment_payment_enabled.map_or(true, |enabled| {
@ -617,7 +773,7 @@ fn filter_installment_based(
}
async fn filter_payment_country_based(
pm: &api::ListPaymentMethod,
pm: &RequestPaymentMethodTypes,
address: Option<&storage::Address>,
) -> errors::CustomResult<bool, errors::ApiErrorResponse> {
Ok(address.map_or(true, |address| {
@ -639,7 +795,7 @@ async fn filter_payment_country_based(
fn filter_payment_currency_based(
payment_intent: &storage::PaymentIntent,
pm: &api::ListPaymentMethod,
pm: &RequestPaymentMethodTypes,
) -> bool {
payment_intent.currency.map_or(true, |currency| {
pm.accepted_currencies.as_ref().map_or(true, |ac| {
@ -658,16 +814,16 @@ fn filter_payment_currency_based(
fn filter_payment_amount_based(
payment_intent: &storage::PaymentIntent,
pm: &api::ListPaymentMethod,
pm: &RequestPaymentMethodTypes,
) -> bool {
let amount = payment_intent.amount;
pm.maximum_amount.map_or(true, |amt| amount < amt)
&& pm.minimum_amount.map_or(true, |amt| amount > amt)
pm.maximum_amount.map_or(true, |amt| amount < amt.into())
&& pm.minimum_amount.map_or(true, |amt| amount > amt.into())
}
async fn filter_payment_mandate_based(
payment_attempt: Option<&storage::PaymentAttempt>,
pm: &api::ListPaymentMethod,
pm: &RequestPaymentMethodTypes,
) -> errors::CustomResult<bool, errors::ApiErrorResponse> {
let recurring_filter = if !pm.recurring_enabled {
payment_attempt.map_or(true, |pa| pa.mandate_id.is_none())
@ -684,7 +840,10 @@ pub async fn list_customer_payment_method(
) -> errors::RouterResponse<api::ListCustomerPaymentMethodsResponse> {
let db = &*state.store;
let all_mcas = db
.find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id)
.find_merchant_connector_account_by_merchant_id_and_disabled_list(
&merchant_account.merchant_id,
false,
)
.await
.map_err(|error| {
error.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
@ -981,13 +1140,9 @@ pub async fn retrieve_payment_method(
payment_method_id: pm.payment_method_id,
payment_method: pm.payment_method.foreign_into(),
payment_method_type: pm.payment_method_type.map(ForeignInto::foreign_into),
payment_method_issuer: pm.payment_method_issuer,
card,
metadata: pm.metadata,
created: Some(pm.created_at),
payment_method_issuer_code: pm
.payment_method_issuer_code
.map(ForeignInto::foreign_into),
recurring_enabled: false, //[#219]
installment_payment_enabled: false, //[#219]
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219],

View File

@ -120,11 +120,9 @@ pub fn mk_add_card_response(
payment_method_id: response.card_id,
payment_method: req.payment_method,
payment_method_type: req.payment_method_type,
payment_method_issuer: req.payment_method_issuer,
card: Some(card),
metadata: req.metadata,
created: Some(common_utils::date_time::now()),
payment_method_issuer_code: req.payment_method_issuer_code,
recurring_enabled: false, // [#256]
installment_payment_enabled: false, // #[#256]
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), // [#256]

View File

@ -286,7 +286,10 @@ where
let supported_connectors: &Vec<String> = state.conf.connectors.supported.wallets.as_ref();
let connector_accounts = db
.find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id)
.find_merchant_connector_account_by_merchant_id_and_disabled_list(
&merchant_account.merchant_id,
false,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Database error when querying for merchant connector accounts")?;

View File

@ -115,9 +115,10 @@ pub trait MerchantConnectorAccountInterface {
merchant_connector_id: &str,
) -> CustomResult<storage::MerchantConnectorAccount, errors::StorageError>;
async fn find_merchant_connector_account_by_merchant_id_list(
async fn find_merchant_connector_account_by_merchant_id_and_disabled_list(
&self,
merchant_id: &str,
get_disabled: bool,
) -> CustomResult<Vec<storage::MerchantConnectorAccount>, errors::StorageError>;
async fn update_merchant_connector_account(
@ -186,12 +187,13 @@ impl MerchantConnectorAccountInterface for Store {
t.insert(&conn).await.map_err(Into::into).into_report()
}
async fn find_merchant_connector_account_by_merchant_id_list(
async fn find_merchant_connector_account_by_merchant_id_and_disabled_list(
&self,
merchant_id: &str,
get_disabled: bool,
) -> CustomResult<Vec<storage::MerchantConnectorAccount>, errors::StorageError> {
let conn = pg_connection(&self.master_pool).await?;
storage::MerchantConnectorAccount::find_by_merchant_id(&conn, merchant_id)
storage::MerchantConnectorAccount::find_by_merchant_id(&conn, merchant_id, get_disabled)
.await
.map_err(Into::into)
.into_report()
@ -293,9 +295,10 @@ impl MerchantConnectorAccountInterface for MockDb {
Ok(account)
}
async fn find_merchant_connector_account_by_merchant_id_list(
async fn find_merchant_connector_account_by_merchant_id_and_disabled_list(
&self,
_merchant_id: &str,
_get_disabled: bool,
) -> CustomResult<Vec<storage::MerchantConnectorAccount>, errors::StorageError> {
// [#172]: Implement function for `MockDb`
Err(errors::StorageError::MockDbError)?

View File

@ -141,7 +141,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::enums::MandateStatus,
api_models::enums::PaymentExperience,
api_models::admin::PaymentConnectorCreate,
api_models::admin::PaymentMethods,
api_models::admin::PaymentMethodsEnabled,
api_models::payments::AddressDetails,
api_models::payments::Address,
api_models::payments::OrderDetails,

View File

@ -1,8 +1,8 @@
pub use api_models::admin::{
CreateMerchantAccount, DeleteMcaResponse, DeleteMerchantAccountResponse,
MerchantAccountResponse, MerchantConnectorId, MerchantDetails, MerchantId,
PaymentConnectorCreate, PaymentMethods, RoutingAlgorithm, ToggleKVRequest, ToggleKVResponse,
WebhookDetails,
PaymentConnectorCreate, PaymentMethodsEnabled, RoutingAlgorithm, ToggleKVRequest,
ToggleKVResponse, WebhookDetails,
};
use crate::types::{storage, transformers::Foreign};