diff --git a/src/decider/gatewaydecider/gw_filter.rs b/src/decider/gatewaydecider/gw_filter.rs index 8276b94c..cb855d5f 100644 --- a/src/decider/gatewaydecider/gw_filter.rs +++ b/src/decider/gatewaydecider/gw_filter.rs @@ -1669,6 +1669,7 @@ pub async fn filterGatewaysForValidationType( (ETGCI::ValidationType::Tpv, e_mgas) }; + if matches!(validation_type,ETGCI::ValidationType::TpvEmandate | ETGCI::ValidationType::Emandate) && is_otm_flow { return Ok(returnGwListWithLog( this, @@ -1692,13 +1693,43 @@ pub async fn filterGatewaysForValidationType( let amount = Utils::effective_amount_with_txn_amount(txn_detail.clone()).await; + let m_pf = Utils::get_pf_from_validation_type(&validation_type); + + let (empty_csi_mgas , gpmf_source_mgas): (Vec<_>, Vec<_>,) = enabled_gateway_accounts.clone().into_iter().partition(|mga| mga.config_source_info.is_none()); + + // --- Second Partition on gpmfSourceMgas --- + let (gpmf_based_mgas, non_gpmf_inferred_mgas): (Vec<_>, Vec<_>) = + gpmf_source_mgas + .into_iter() + .partition(|mga| { + let config_info_opt = &mga.config_source_info; + + let gpmf_flows_opt: Option<&Vec> = config_info_opt + .as_ref() + .and_then(|info| info.gpmf_inferred_flows.as_ref()); + + let is_gpmf_flows_empty = gpmf_flows_opt.map_or(true, |flows| flows.is_empty()); + + let m_pf_is_present_in_gpmf_flows = m_pf + .as_ref() + .map_or(false, |pf_val| { + gpmf_flows_opt.map_or(false, |flows_vec_opt| { flows_vec_opt.contains(pf_val)}) + }); + is_gpmf_flows_empty || m_pf_is_present_in_gpmf_flows + }); + + let gci_based_mgas: Vec = empty_csi_mgas + .into_iter() + .chain(non_gpmf_inferred_mgas.into_iter()) + .collect(); + // Filter gateways for payment method and validation type let merchant_gateway_card_infos = SETMCI::filter_gateways_for_payment_method_and_validation_type( this.state(), macc, txn_card_info.clone(), - enabled_gateway_accounts.clone(), + gci_based_mgas.clone(), validation_type, transaction_id_to_text(txn_detail.txnId.clone()), ) @@ -1708,9 +1739,9 @@ pub async fn filterGatewaysForValidationType( let merchant_gateway_card_infos = Utils::filter_gateway_card_info_for_max_register_amount( txn_detail.clone(), - txn_card_info, + txn_card_info.clone(), merchant_gateway_card_infos, - amount, + amount.clone(), ); // Extract gateway card info IDs @@ -1734,10 +1765,7 @@ pub async fn filterGatewaysForValidationType( let new_st = catMaybes(&nst); - // Update gateway list and MGAs - setGwsAndMgas( - this, - enabled_gateway_accounts + let mgci_matched_final_mgas: Vec = gci_based_mgas .into_iter() .filter(|mga| { new_st.contains(&mga.gateway) @@ -1747,8 +1775,65 @@ pub async fn filterGatewaysForValidationType( .collect::>() .contains(&Some(mga.id.clone())) }) - .collect(), - ); + .collect(); + + let mgpmf_matched_final_mgas = match (m_pf.as_ref(), gpmf_based_mgas.is_empty()) { + (Some(pf), false) => { + let maybe_jbc = find_bank_code(txn_card_info.paymentMethod).await; + match maybe_jbc { + None => vec![], + Some(jbc) => { + let gw_list: Vec = gpmf_based_mgas + .iter() + .map(|mga| mga.gateway.clone()) + .collect(); + + let all_gpmf_entries = GPMF::find_all_gpmf_by_country_code_gw_pf_id_pmt_jbcid_db( + crate::types::country::country_iso::CountryISO::IND, + gw_list, + pf.clone(), + txn_card_info.paymentMethodType.clone(), + jbc.id, + ) + .await + .unwrap_or_default(); + + let mga_ids = gpmf_based_mgas + .iter() + .map(|mga| mga.id.merchantGwAccId) + .collect::>(); + + let gpmf_ids: Vec = all_gpmf_entries + .iter() + .map(|entry| to_gateway_payment_method_flow_id(entry.id.clone())) + .collect(); + + let mgpmf_entries = + MGPMF::get_all_mgpmf_by_mga_id_and_gpmf_ids(mga_ids, gpmf_ids).await; + + let max_amount_filtered_entries = Utils::filter_mgpmf_for_max_register_amount( + pf.clone(), + &txn_card_info.paymentMethodType, + mgpmf_entries.clone(), + amount, + ); + + let mgpmf_mga_id_entries = max_amount_filtered_entries + .await.iter() + .map(|entry| entry.merchantGatewayAccountId) + .collect::>(); + + gpmf_based_mgas + .into_iter() + .filter(|mga| mgpmf_mga_id_entries.contains(&mga.id.merchantGwAccId)) + .collect() + } + } + } + _ => vec![], + }; + let new_gws = [mgci_matched_final_mgas, mgpmf_matched_final_mgas].concat(); + setGwsAndMgas(this, new_gws); } } // Handle other transaction types diff --git a/src/decider/gatewaydecider/utils.rs b/src/decider/gatewaydecider/utils.rs index 2e35a2bf..1960f600 100644 --- a/src/decider/gatewaydecider/utils.rs +++ b/src/decider/gatewaydecider/utils.rs @@ -6,7 +6,7 @@ use crate::types::card::card_type::card_type_to_text; use crate::types::merchant::id::{merchant_id_to_text, MerchantId}; use crate::types::merchant::merchant_gateway_account::MerchantGatewayAccount; use crate::types::money::internal::Money; -use crate::types::payment_flow::{payment_flows_to_text, PaymentFlow}; +use crate::types::payment_flow::{payment_flows_to_text, to_flow_level_id, FlowLevel, FlowLevelId, MicroPaymentFlowName, PaymentFlow}; use crate::types::user_eligibility_info::{ get_eligibility_info, identifier_name_to_text, IdentifierName, }; @@ -85,6 +85,8 @@ use crate::types::isin_routes as ETIsinR; // // use configs::env_vars as ENV; use crate::types::gateway_card_info::ValidationType; use crate::error::StorageError; +use crate::types::merchant_gateway_payment_method_flow::{ MerchantGatewayPaymentMethodFlow}; +use crate::types::micro_payment_flow:: {find_mpf_by_flow_level_flow_level_ids_mpf_name, MicroPaymentFlowF}; pub fn either_decode_t Deserialize<'de>>(text: &str) -> Result { from_slice(text.as_bytes()).map_err(|e| e.to_string()) @@ -616,6 +618,10 @@ pub fn is_emandate_register_transaction(txn_detail: &ETTD::TxnDetail) -> bool { txn_detail.txnObjectType == ETTD::TxnObjectType::EmandateRegister } +fn is_pmt_eligible_for_emandate_amount_filter(pmt: &PaymentMethodType) -> bool { + matches!(pmt, PaymentMethodType::Card | PaymentMethodType::NB | PaymentMethodType::Aadhaar | PaymentMethodType::PAN) +} + pub async fn get_card_brand(decider_flow: &mut DeciderFlow<'_>) -> Option { let c_card_brand = decider_flow.writer.cardBrand.clone(); if let Some(cb) = c_card_brand { @@ -2727,6 +2733,70 @@ pub async fn get_penality_factor_( } } +pub fn get_pf_from_validation_type(vt: &ValidationType) -> Option { + match vt { + ValidationType::Tpv => Some(PaymentFlow::TPV), + ValidationType::TpvEmandate => Some(PaymentFlow::TPV_EMANDATE), + ValidationType::Emandate => Some(PaymentFlow::EMANDATE), + _ => None, + } +} + +pub async fn filter_mgpmf_for_max_register_amount( + pf: PaymentFlow, + payment_method_type: &PaymentMethodType, + merchant_gateway_payment_method_flows: Vec, + txn_amount: Money, +) -> Vec { + if matches!(pf, PaymentFlow::EMANDATE | PaymentFlow::TPV_EMANDATE) + && is_pmt_eligible_for_emandate_amount_filter(payment_method_type) + { + let flow_level_ids = extract_mgpmf_ids_as_flow_level_ids(&merchant_gateway_payment_method_flows); + + let min_amount = Money::from_double(10000.0); + + let mpfs: Vec = find_mpf_by_flow_level_flow_level_ids_mpf_name(FlowLevel::MerchantGatewayPaymentMethodFlow, flow_level_ids, MicroPaymentFlowName::RegisterMaxAmount).await; + + let filtered_ids: Vec = mpfs + .iter() + .filter_map(|mpf| { + let val= from_str(&mpf.value).ok().unwrap_or(min_amount.clone()); + if txn_amount <= val { + Some(mpf.flowLevelId.clone()) + } else { + None + } + }) + .collect(); + + merchant_gateway_payment_method_flows + .into_iter() + .filter_map(|mgpmf| { + match mgpmf.id.map(|id| to_flow_level_id(id.to_string())) { + Some(value) => if filtered_ids.contains(&value) { + Some(mgpmf) + } else { + None + } + _ => None + } + + + }) + .collect() + } else { + merchant_gateway_payment_method_flows + } +} + +fn extract_mgpmf_ids_as_flow_level_ids( + flows: &[MerchantGatewayPaymentMethodFlow], +) -> Vec { + flows + .iter() + .filter_map(|flow| flow.id.map(|id| to_flow_level_id(id.to_string()))) + .collect() +} // fn push_to_stream(decided_gateway: OptionETG::Gateway, final_decider_approach: types::GatewayDeciderApproach, m_priority_logic_tag: Option, current_gateway_score_map: GatewayScoreMap) -> DeciderFlow<()> { // if let Some(decided_gateway) = decided_gateway { diff --git a/src/storage/schema.rs b/src/storage/schema.rs index bd3cbfe4..feaf6955 100644 --- a/src/storage/schema.rs +++ b/src/storage/schema.rs @@ -253,6 +253,7 @@ diesel::table! { gateway_identifier -> Nullable, gateway_type -> Nullable, supported_txn_type -> Nullable, + config_source_info -> Nullable, } } @@ -293,6 +294,20 @@ diesel::table! { } } +diesel::table! { + micro_payment_flow (id) { + id -> Text, + flow_level -> Text, + flow_level_id -> Text, + micro_payment_flow_name -> Text, + #[sql_name = "type"] + value_type -> Text, + value -> Text, + date_created -> Datetime, + last_updated -> Datetime, + } +} + diesel::table! { merchant_iframe_preferences (id) { id -> Bigint, @@ -546,3 +561,4 @@ diesel::allow_tables_to_appear_in_same_query!( txn_offer_detail, user_eligibility_info, ); + diff --git a/src/storage/types.rs b/src/storage/types.rs index af24889c..64be4ff1 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -332,6 +332,7 @@ pub struct MerchantGatewayAccount { pub gateway_identifier: Option, pub gateway_type: Option, pub supported_txn_type: Option, + pub config_source_info: Option, } #[derive(Debug, Clone, Identifiable, Queryable)] @@ -373,6 +374,19 @@ pub struct MerchantGatewayPaymentMethodFlow { pub gateway_bank_code: Option, } +#[derive(Debug, Clone, Identifiable, Queryable)] +#[cfg_attr(feature = "mysql", diesel(table_name = schema::micro_payment_flow))] +#[cfg_attr(feature = "postgres", diesel(table_name = schema_pg::micro_payment_flow))] +pub struct MicroPaymentFlow { + pub id: String, + pub flow_level: String, + pub flow_level_id: String, + pub micro_payment_flow_name: String, + pub value_type: String, + pub value: String, + pub date_created: PrimitiveDateTime, + pub last_updated: PrimitiveDateTime, +} #[derive(Debug, Clone, PartialEq, FromSqlRow, AsExpression, Serialize)] #[cfg_attr(feature = "mysql", diesel(sql_type = Bit))] diff --git a/src/types.rs b/src/types.rs index 47f6d310..81cdd381 100644 --- a/src/types.rs +++ b/src/types.rs @@ -38,3 +38,4 @@ pub mod txn_offer; pub mod txn_offer_detail; pub mod txn_offer_info; pub mod user_eligibility_info; +pub mod micro_payment_flow; diff --git a/src/types/merchant/merchant_gateway_account.rs b/src/types/merchant/merchant_gateway_account.rs index aecc9eb3..a3d8d324 100644 --- a/src/types/merchant/merchant_gateway_account.rs +++ b/src/types/merchant/merchant_gateway_account.rs @@ -25,6 +25,7 @@ use crate::storage::schema_pg::merchant_gateway_account::dsl; use diesel::associations::HasTable; use diesel::*; use std::fmt::Debug; +use crate::types::payment_flow::PaymentFlow; // #[derive(Debug, PartialEq, Serialize, Deserialize)] // pub struct EulerAccountDetails { @@ -86,6 +87,25 @@ pub fn to_mga_reference_id(id: String) -> MgaReferenceId { } } + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)] +enum FlowConfigSource { GPMF} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ConfigSourceInfo { + #[serde(rename = "flowConfigSource")] + pub flow_config_source: FlowConfigSource, + #[serde(rename = "gpmfInferredFlows")] + pub gpmf_inferred_flows: Option>, +} + +fn to_config_source_info(csi: String) -> Result { + match serde_json::from_str::(&csi) { + Ok(res) => Ok(res), + Err(_) => Err(ApiError::ParsingError("Invalid ConfigSourceInfo value")), + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MerchantGatewayAccount { #[serde(rename = "id")] @@ -112,6 +132,8 @@ pub struct MerchantGatewayAccount { pub gatewayType: Option, #[serde(rename = "supportedTxnType")] pub supportedTxnType: Option, + #[serde(rename = "configSourceInfo")] + pub config_source_info: Option, } impl TryFrom for MerchantGatewayAccount { @@ -134,6 +156,7 @@ impl TryFrom for MerchantGatewayAccount { gatewayIdentifier: value.gateway_identifier, gatewayType: value.gateway_type, supportedTxnType: value.supported_txn_type, + config_source_info: value.config_source_info.map(|csi| to_config_source_info(csi)).transpose()?, }) } } diff --git a/src/types/micro_payment_flow.rs b/src/types/micro_payment_flow.rs new file mode 100644 index 00000000..2875f7f5 --- /dev/null +++ b/src/types/micro_payment_flow.rs @@ -0,0 +1,128 @@ +use crate::app::get_tenant_app_state; +use crate::types::payment_flow::*; +use serde::{Deserialize, Serialize}; +use std::string::String; +use std::time::SystemTime; +use std::vec::Vec; +use crate::storage::types::{MicroPaymentFlow as DBMicroPaymentFlow}; + +use super::payment_flow::{FlowLevel, MicroPaymentFlowName, FlowLevelId, MicroPaymentFlowType, to_flow_level_id, text_to_flow_level, text_to_micro_payment_flow_name, text_to_micro_payment_flow_type + }; +#[cfg(feature = "mysql")] +use crate::storage::schema::micro_payment_flow::dsl; +#[cfg(feature = "postgres")] +use crate::storage::schema_pg::micro_payment_flow::dsl; +use diesel::associations::HasTable; +use diesel::*; +use crate::logger; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct MicroPaymentFlowId { + pub microPaymentFlowId: String, +} + +pub fn to_micro_payment_flow_id(id: String) -> MicroPaymentFlowId { + MicroPaymentFlowId { + microPaymentFlowId: id, + } +} + +pub fn micro_payment_flow_id_text(id: MicroPaymentFlowId) -> String { + id.microPaymentFlowId +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MicroPaymentFlowF { + #[serde(rename = "id")] + pub id: MicroPaymentFlowId, + #[serde(rename = "flowLevel")] + pub flowLevel: FlowLevel, + #[serde(rename = "flowLevelId")] + pub flowLevelId: FlowLevelId, + #[serde(rename = "value")] + pub value: String, + #[serde(rename = "microPaymentFlowName")] + pub microPaymentFlowName: MicroPaymentFlowName, + #[serde(rename = "valueType")] + pub valueType: MicroPaymentFlowType, + #[serde(rename = "dateCreated")] + pub dateCreated: SystemTime, + #[serde(rename = "lastUpdated")] + pub lastUpdated: SystemTime, +} + +impl TryFrom for MicroPaymentFlowF { + type Error = crate::error::ApiError; + + fn try_from(db: DBMicroPaymentFlow) -> Result { + // Convert string IDs to domain types + let id = to_micro_payment_flow_id(db.id); + let flow_level = text_to_flow_level(db.flow_level)?; + let flow_level_id = to_flow_level_id(db.flow_level_id); + let micro_payment_flow_name = text_to_micro_payment_flow_name(db.micro_payment_flow_name)?; + let value_type = text_to_micro_payment_flow_type(db.value_type)?; + + // Construct the GatewayPaymentMethodFlow instance + Ok(Self { + id, + flowLevel:flow_level, + flowLevelId:flow_level_id, + dateCreated: db.date_created.assume_utc().into(), + lastUpdated: db.last_updated.assume_utc().into(), + microPaymentFlowName:micro_payment_flow_name, + valueType:value_type, + value: db.value, + }) + } +} + +pub async fn find_mpf_by_flow_level_flow_level_ids_mpf_name_db( + flow_level: FlowLevel, + flow_level_ids: Vec, + mpf_name: MicroPaymentFlowName, +) -> Result, crate::generics::MeshError> { + let app_state = get_tenant_app_state().await; + + // Extract and convert various IDs to their string representations + let flow_level_text = flow_level_to_text(&flow_level); + let mpfn_text = micro_payment_flow_name_to_text(&mpf_name); + let flow_level_ids_text: Vec = flow_level_ids.into_iter().map(|fl_id| fl_id.flowLevelId).collect(); + + + // Use Diesel's query builder with multiple conditions + crate::generics::generic_find_all::< + ::Table, + _, + DBMicroPaymentFlow, + >( + &app_state.db, + dsl::flow_level_id + .eq_any(flow_level_ids_text) + .and(dsl::flow_level.eq(flow_level_text)) + .and(dsl::micro_payment_flow_name.eq(mpfn_text)), + ) + .await +} + +pub async fn find_mpf_by_flow_level_flow_level_ids_mpf_name( + flow_level: FlowLevel, + flow_level_ids: Vec, + mpf_name: MicroPaymentFlowName, +) -> Vec { + // Call the DB function and handle the results + match find_mpf_by_flow_level_flow_level_ids_mpf_name_db(flow_level, flow_level_ids, mpf_name) + .await + { + Ok(db_results) => db_results + .into_iter() + .filter_map(|db_record: DBMicroPaymentFlow| { + MicroPaymentFlowF::try_from(db_record).ok() + }) + .collect(), + Err(err) => { + logger::info!("Error in find_mpf_by_flow_level_flow_level_ids_mpf_name: {:?}", err); + Vec::new() // Silently handle any errors by returning empty vec + } + } +} + diff --git a/src/types/payment_flow.rs b/src/types/payment_flow.rs index df01244c..884d98a3 100644 --- a/src/types/payment_flow.rs +++ b/src/types/payment_flow.rs @@ -504,3 +504,82 @@ pub struct AddressVerification { #[serde(rename = "collectAvsInfo")] pub collectAvsInfo: Option, } + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum FlowLevel { + PaymentFlow, + GatewayPaymentFlow, + GatewayPaymentMethodFlow, + MerchantGatewayPaymentMethodFlow, +} + +pub fn text_to_flow_level(text: String) -> Result { + match text.as_str() { + "PAYMENT_FLOW" => Ok(FlowLevel::PaymentFlow), + "GATEWAY_PAYMENT_FLOW" => Ok(FlowLevel::GatewayPaymentFlow), + "GATEWAY_PAYMENT_METHOD_FLOW" => Ok(FlowLevel::GatewayPaymentMethodFlow), + "MERCHANT_GATEWAY_PAYMENT_METHOD_FLOW" => Ok(FlowLevel::MerchantGatewayPaymentMethodFlow), + _ => Err(ApiError::ParsingError("Invalid FlowLevel")), + } +} + + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + + +pub struct FlowLevelId { + pub flowLevelId: String, +} + +pub fn to_flow_level_id(id: String) -> FlowLevelId { + FlowLevelId { + flowLevelId: id, + } +} + + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum MicroPaymentFlowName { + RegisterMaxAmount, + SupportedFrequencies, + BankAccountDetailsSupportMode, +} + +pub fn text_to_micro_payment_flow_name(text: String) -> Result { + match text.as_str() { + "REGISTER_MAX_AMOUNT" => Ok(MicroPaymentFlowName::RegisterMaxAmount), + "SUPPORTED_FREQUENCIES" => Ok(MicroPaymentFlowName::SupportedFrequencies), + "BANK_ACCOUNT_DETAILS_SUPPORT_MODE" => Ok(MicroPaymentFlowName::BankAccountDetailsSupportMode), + _ => Err(ApiError::ParsingError("Invalid MicroPaymentFlowName")), + } +} + +pub fn text_to_micro_payment_flow_type(text: String) -> Result { + match text.as_str() { + "ARRAY" => Ok(MicroPaymentFlowType::ARRAY), + "BOOLEAN" => Ok(MicroPaymentFlowType::BOOLEAN), + "DOUBLE" => Ok(MicroPaymentFlowType::DOUBLE), + "OBJECT" => Ok(MicroPaymentFlowType::OBJECT), + "STRING" => Ok(MicroPaymentFlowType::STRING), + _ => Err(ApiError::ParsingError("Invalid MicroPaymentFlowType")), + } +} + +pub fn micro_payment_flow_name_to_text(mpfn: &MicroPaymentFlowName) -> String { + match mpfn { + MicroPaymentFlowName::RegisterMaxAmount => "REGISTER_MAX_AMOUNT".to_string(), + MicroPaymentFlowName::SupportedFrequencies => "SUPPORTED_FREQUENCIES".to_string(), + MicroPaymentFlowName::BankAccountDetailsSupportMode => "BANK_ACCOUNT_DETAILS_SUPPORT_MODE".to_string(), + } +} + +pub fn flow_level_to_text(mpfn: &FlowLevel) -> String { + match mpfn { + FlowLevel::PaymentFlow => "PAYMENT_FLOW".to_string(), + FlowLevel::GatewayPaymentFlow => "GATEWAY_PAYMENT_FLOW".to_string(), + FlowLevel::GatewayPaymentMethodFlow => "GATEWAY_PAYMENT_METHOD_FLOW".to_string(), + FlowLevel::MerchantGatewayPaymentMethodFlow => "MERCHANT_GATEWAY_PAYMENT_METHOD_FLOW".to_string(), + } +} \ No newline at end of file