diff --git a/Cargo.lock b/Cargo.lock index bddc9e50df..092a1018cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1743,6 +1743,7 @@ dependencies = [ "common_enums", "common_utils", "error-stack", + "masking", "serde", "serde_json", "strum 0.25.0", @@ -4518,18 +4519,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.164" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -4538,11 +4539,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.0.0", "itoa", "ryu", "serde", @@ -4842,6 +4843,7 @@ dependencies = [ "ring", "router_env", "serde", + "serde_json", "thiserror", "tokio", ] diff --git a/crates/common_enums/Cargo.toml b/crates/common_enums/Cargo.toml index 7b930d073d..10b4fb509e 100644 --- a/crates/common_enums/Cargo.toml +++ b/crates/common_enums/Cargo.toml @@ -15,7 +15,7 @@ diesel = { version = "2.1.0", features = ["postgres"] } serde = { version = "1.0.160", features = [ "derive" ] } serde_json = "1.0.96" strum = { version = "0.25", features = [ "derive" ] } -time = { version = "0.3.20", features = ["serde", "serde-well-known", "std"] } +time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } utoipa = { version = "3.3.0", features = ["preserve_order"] } # First party crates diff --git a/crates/data_models/Cargo.toml b/crates/data_models/Cargo.toml index e0b47bf129..254c194182 100644 --- a/crates/data_models/Cargo.toml +++ b/crates/data_models/Cargo.toml @@ -15,6 +15,7 @@ olap = [] [dependencies] # First party deps api_models = { version = "0.1.0", path = "../api_models" } +masking = { version = "0.1.0", path = "../masking" } common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils" } diff --git a/crates/data_models/src/mandates.rs b/crates/data_models/src/mandates.rs index 6ea40a286e..afdcda3a40 100644 --- a/crates/data_models/src/mandates.rs +++ b/crates/data_models/src/mandates.rs @@ -1,5 +1,12 @@ +use api_models::payments::{ + AcceptanceType as ApiAcceptanceType, CustomerAcceptance as ApiCustomerAcceptance, + MandateAmountData as ApiMandateAmountData, MandateData as ApiMandateData, MandateType, + OnlineMandate as ApiOnlineMandate, +}; use common_enums::Currency; -use common_utils::pii; +use common_utils::{date_time, errors::ParsingError, pii}; +use error_stack::{IntoReport, ResultExt}; +use masking::{PeekInterface, Secret}; use time::PrimitiveDateTime; #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] @@ -17,3 +24,135 @@ pub struct MandateAmountData { pub end_date: Option, pub metadata: Option, } + +// The fields on this struct are optional, as we want to allow the merchant to provide partial +// information about creating mandates +#[derive(Default, Eq, PartialEq, Debug, Clone)] +pub struct MandateData { + /// A concent from the customer to store the payment method + pub customer_acceptance: Option, + /// A way to select the type of mandate used + pub mandate_type: Option, +} + +#[derive(Default, Eq, PartialEq, Debug, Clone)] +pub struct CustomerAcceptance { + /// Type of acceptance provided by the + pub acceptance_type: AcceptanceType, + /// Specifying when the customer acceptance was provided + pub accepted_at: Option, + /// Information required for online mandate generation + pub online: Option, +} + +#[derive(Default, Debug, PartialEq, Eq, Clone)] +pub enum AcceptanceType { + Online, + #[default] + Offline, +} + +#[derive(Default, Eq, PartialEq, Debug, Clone)] +pub struct OnlineMandate { + /// Ip address of the customer machine from which the mandate was created + pub ip_address: Option>, + /// The user-agent of the customer's browser + pub user_agent: String, +} + +impl From for MandateDataType { + fn from(mandate_type: MandateType) -> Self { + match mandate_type { + MandateType::SingleUse(mandate_amount_data) => { + Self::SingleUse(mandate_amount_data.into()) + } + MandateType::MultiUse(mandate_amount_data) => { + Self::MultiUse(mandate_amount_data.map(|d| d.into())) + } + } + } +} + +impl From for MandateAmountData { + fn from(value: ApiMandateAmountData) -> Self { + Self { + amount: value.amount, + currency: value.currency, + start_date: value.start_date, + end_date: value.end_date, + metadata: value.metadata, + } + } +} + +impl From for MandateData { + fn from(value: ApiMandateData) -> Self { + Self { + customer_acceptance: value.customer_acceptance.map(|d| d.into()), + mandate_type: value.mandate_type.map(|d| d.into()), + } + } +} + +impl From for CustomerAcceptance { + fn from(value: ApiCustomerAcceptance) -> Self { + Self { + acceptance_type: value.acceptance_type.into(), + accepted_at: value.accepted_at, + online: value.online.map(|d| d.into()), + } + } +} + +impl From for AcceptanceType { + fn from(value: ApiAcceptanceType) -> Self { + match value { + ApiAcceptanceType::Online => Self::Online, + ApiAcceptanceType::Offline => Self::Offline, + } + } +} + +impl From for OnlineMandate { + fn from(value: ApiOnlineMandate) -> Self { + Self { + ip_address: value.ip_address, + user_agent: value.user_agent, + } + } +} + +impl CustomerAcceptance { + pub fn get_ip_address(&self) -> Option { + self.online + .as_ref() + .and_then(|data| data.ip_address.as_ref().map(|ip| ip.peek().to_owned())) + } + + pub fn get_user_agent(&self) -> Option { + self.online.as_ref().map(|data| data.user_agent.clone()) + } + + pub fn get_accepted_at(&self) -> PrimitiveDateTime { + self.accepted_at + .unwrap_or_else(common_utils::date_time::now) + } +} + +impl MandateAmountData { + pub fn get_end_date( + &self, + format: date_time::DateFormat, + ) -> error_stack::Result, ParsingError> { + self.end_date + .map(|date| { + date_time::format_date(date, format) + .into_report() + .change_context(ParsingError::DateTimeParsingError) + }) + .transpose() + } + pub fn get_metadata(&self) -> Option { + self.metadata.clone() + } +} diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index d6b9d48f03..68e083abd1 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -1,3 +1,4 @@ +use api_models::enums::Connector; use common_enums as storage_enums; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -77,6 +78,15 @@ pub trait PaymentAttemptInterface { merchant_id: &str, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result; + + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &str, + active_attempt_ids: &[String], + connector: Option>, + payment_methods: Option>, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result; } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -129,7 +139,7 @@ pub struct PaymentAttempt { pub connector_response_reference_id: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct PaymentListFilters { pub connector: Vec, pub currency: Vec, @@ -222,6 +232,13 @@ pub enum PaymentAttemptUpdate { payment_experience: Option, business_sub_label: Option, straight_through_algorithm: Option, + error_code: Option>, + error_message: Option>, + }, + RejectUpdate { + status: storage_enums::AttemptStatus, + error_code: Option>, + error_message: Option>, }, VoidUpdate { status: storage_enums::AttemptStatus, @@ -261,9 +278,8 @@ pub enum PaymentAttemptUpdate { error_message: Option>, error_reason: Option>, }, - MultipleCaptureUpdate { - status: Option, - multiple_capture_count: Option, + MultipleCaptureCountUpdate { + multiple_capture_count: i16, }, PreprocessingUpdate { status: storage_enums::AttemptStatus, diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index d87b8715f7..c22151d380 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -14,7 +14,6 @@ use crate::{ errors::{self, DatabaseError}, payment_attempt::{ PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate, PaymentAttemptUpdateInternal, - PaymentListFilters, }, payment_intent::PaymentIntent, query::generics::db_metrics, @@ -212,15 +211,20 @@ impl PaymentAttempt { conn: &PgPooledConn, pi: &[PaymentIntent], merchant_id: &str, - ) -> StorageResult { - let active_attempt_ids: Vec = pi + ) -> StorageResult<( + Vec, + Vec, + Vec, + Vec, + )> { + let active_attempts: Vec = pi .iter() .map(|payment_intent| payment_intent.clone().active_attempt_id) .collect(); let filter = ::table() .filter(dsl::merchant_id.eq(merchant_id.to_owned())) - .filter(dsl::attempt_id.eq_any(active_attempt_ids)); + .filter(dsl::attempt_id.eq_any(active_attempts)); let intent_status: Vec = pi .iter() @@ -268,14 +272,12 @@ impl PaymentAttempt { .flatten() .collect::>(); - let filters = PaymentListFilters { - connector: filter_connector, - currency: filter_currency, - status: intent_status, - payment_method: filter_payment_method, - }; - - Ok(filters) + Ok(( + filter_connector, + filter_currency, + intent_status, + filter_payment_method, + )) } pub async fn get_total_count_of_attempts( conn: &PgPooledConn, diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index b8c5caf178..21b7295541 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -4,6 +4,7 @@ use common_utils::{ date_time, fp_utils, pii, pii::Email, }; +use data_models::mandates::MandateDataType; use error_stack::{IntoReport, ResultExt}; use masking::{PeekInterface, Secret}; use reqwest::Url; @@ -11,8 +12,8 @@ use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ - self, AddressDetailsData, BrowserInformationData, MandateData, - PaymentsAuthorizeRequestData, PaymentsCancelRequestData, RouterData, + self, AddressDetailsData, BrowserInformationData, PaymentsAuthorizeRequestData, + PaymentsCancelRequestData, RouterData, }, consts, core::errors, @@ -790,20 +791,28 @@ fn get_card_info( .change_context(errors::ConnectorError::MissingRequiredField { field_name: "mandate_type", })? { - payments::MandateType::SingleUse(details) => details, - payments::MandateType::MultiUse(details) => { + MandateDataType::SingleUse(details) => details, + MandateDataType::MultiUse(details) => { details.ok_or(errors::ConnectorError::MissingRequiredField { field_name: "mandate_data.mandate_type.multi_use", })? } }; - let mandate_meta: NuveiMandateMeta = - utils::to_connector_meta_from_secret(Some(details.get_metadata()?))?; + let mandate_meta: NuveiMandateMeta = utils::to_connector_meta_from_secret( + Some(details.get_metadata().ok_or_else(utils::missing_field_err( + "mandate_data.mandate_type.{multi_use|single_use}.metadata", + ))?), + )?; ( Some("0".to_string()), // In case of first installment, rebilling should be 0 Some(V2AdditionalParams { rebill_expiry: Some( - details.get_end_date(date_time::DateFormat::YYYYMMDD)?, + details + .get_end_date(date_time::DateFormat::YYYYMMDD) + .change_context(errors::ConnectorError::DateFormattingFailed)? + .ok_or_else(utils::missing_field_err( + "mandate_data.mandate_type.{multi_use|single_use}.end_date", + ))?, ), rebill_frequency: Some(mandate_meta.frequency), challenge_window_size: None, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 3865e19858..fd49046bf3 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -6,6 +6,7 @@ use common_utils::{ ext_traits::{ByteSliceExt, BytesExt}, pii::{self, Email}, }; +use data_models::mandates::AcceptanceType; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, ExposeOptionInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; @@ -1672,7 +1673,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { .map(|customer_acceptance| { Ok::<_, error_stack::Report>( match customer_acceptance.acceptance_type { - payments::AcceptanceType::Online => { + AcceptanceType::Online => { let online_mandate = customer_acceptance .online .clone() @@ -1696,7 +1697,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { }, } } - payments::AcceptanceType::Offline => StripeMandateRequest { + AcceptanceType::Offline => StripeMandateRequest { mandate_type: StripeMandateType::Offline, }, }, diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index f5b004046f..34268870fb 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -322,5 +322,5 @@ pub trait MandateBehaviour { fn get_mandate_id(&self) -> Option<&api_models::payments::MandateIds>; fn set_mandate_id(&mut self, new_mandate_id: Option); fn get_payment_method_data(&self) -> api_models::payments::PaymentMethodData; - fn get_setup_mandate_details(&self) -> Option<&api_models::payments::MandateData>; + fn get_setup_mandate_details(&self) -> Option<&data_models::mandates::MandateData>; } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index fffd127937..6a8f804759 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -48,7 +48,7 @@ use crate::{ types::{decrypt, encrypt_optional, AsyncLift}, }, storage::{self, enums}, - transformers::{ForeignFrom, ForeignInto}, + transformers::ForeignFrom, }, utils::{self, ConnectorResponseExt, OptionExt}, }; @@ -1249,9 +1249,31 @@ pub async fn list_payment_methods( redirect_url: merchant_account.return_url, merchant_name: merchant_account.merchant_name, payment_methods: payment_method_responses, - mandate_payment: payment_attempt - .and_then(|inner| inner.mandate_details) - .map(ForeignInto::foreign_into), + mandate_payment: payment_attempt.and_then(|inner| inner.mandate_details).map( + |d| match d { + data_models::mandates::MandateDataType::SingleUse(i) => { + api::MandateType::SingleUse(api::MandateAmountData { + amount: i.amount, + currency: i.currency, + start_date: i.start_date, + end_date: i.end_date, + metadata: i.metadata, + }) + } + data_models::mandates::MandateDataType::MultiUse(Some(i)) => { + api::MandateType::MultiUse(Some(api::MandateAmountData { + amount: i.amount, + currency: i.currency, + start_date: i.start_date, + end_date: i.end_date, + metadata: i.metadata, + })) + } + data_models::mandates::MandateDataType::MultiUse(None) => { + api::MandateType::MultiUse(None) + } + }, + ), }, )) } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index b5f8f22363..a47f52c65a 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -11,6 +11,7 @@ use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant}; use api_models::payments::HeaderPayload; use common_utils::{ext_traits::AsyncExt, pii}; +use data_models::mandates::MandateData; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{IntoReport, ResultExt}; use futures::future::join_all; @@ -31,6 +32,8 @@ use self::{ operations::{payment_complete_authorize, BoxedOperation, Operation}, }; use super::errors::StorageErrorExt; +#[cfg(feature = "olap")] +use crate::types::transformers::ForeignFrom; use crate::{ configs::settings::PaymentMethodTypeTokenFilter, core::{ @@ -1378,7 +1381,7 @@ where pub mandate_id: Option, pub mandate_connector: Option, pub currency: storage_enums::Currency, - pub setup_mandate: Option, + pub setup_mandate: Option, pub address: PaymentAddress, pub token: Option, pub confirm: Option, @@ -1505,7 +1508,7 @@ pub async fn list_payments( merchant: domain::MerchantAccount, constraints: api::PaymentListConstraints, ) -> RouterResponse { - use crate::types::transformers::ForeignFrom; + use data_models::errors::StorageError; helpers::validate_payment_list_request(&constraints)?; let merchant_id = &merchant.merchant_id; @@ -1527,16 +1530,16 @@ pub async fn list_payments( .await { Ok(pa) => Some(Ok((pi, pa))), - Err(e) => { - if e.current_context().is_db_not_found() { + Err(error) => { + if matches!(error.current_context(), StorageError::ValueNotFound(_)) { logger::warn!( - "payment_attempts missing for payment_id : {} | error : {}", + ?error, + "payment_attempts missing for payment_id : {}", pi.payment_id, - e ); return None; } - Some(Err(e)) + Some(Err(error)) } } } @@ -1571,10 +1574,6 @@ pub async fn apply_filters_on_payments( merchant: domain::MerchantAccount, constraints: api::PaymentListFilterConstraints, ) -> RouterResponse { - use storage_impl::DataModelExt; - - use crate::types::transformers::ForeignFrom; - let limit = &constraints.limit; helpers::validate_payment_list_request_for_joins(*limit)?; @@ -1587,7 +1586,7 @@ pub async fn apply_filters_on_payments( .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)? .into_iter() - .map(|(pi, pa)| (pi, pa.to_storage_model())) + .map(|(pi, pa)| (pi, pa)) .collect(); let data: Vec = @@ -1628,8 +1627,6 @@ pub async fn get_filters_for_payments( merchant: domain::MerchantAccount, time_range: api::TimeRange, ) -> RouterResponse { - use crate::types::transformers::ForeignFrom; - let pi = db .filter_payment_intents_by_time_range_constraints( &merchant.merchant_id, @@ -1649,9 +1646,14 @@ pub async fn get_filters_for_payments( .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - let filters: api::PaymentListFilters = ForeignFrom::foreign_from(filters); - - Ok(services::ApplicationResponse::Json(filters)) + Ok(services::ApplicationResponse::Json( + api::PaymentListFilters { + connector: filters.connector, + currency: filters.currency, + status: filters.status, + payment_method: filters.payment_method, + }, + )) } pub async fn add_process_sync_task( @@ -1844,7 +1846,7 @@ pub fn decide_connector( if let Some(ref connector_name) = routing_data.routed_through { let connector_data = api::ConnectorData::get_connector_by_name( &state.conf.connectors, - connector_name, + connector_name.as_str(), api::GetToken::Connector, ) .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 7793a7eb0d..272ccfced6 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -248,7 +248,7 @@ impl mandate::MandateBehaviour for types::PaymentsAuthorizeData { fn get_setup_future_usage(&self) -> Option { self.setup_future_usage } - fn get_setup_mandate_details(&self) -> Option<&api_models::payments::MandateData> { + fn get_setup_mandate_details(&self) -> Option<&data_models::mandates::MandateData> { self.setup_mandate_details.as_ref() } diff --git a/crates/router/src/core/payments/flows/verify_flow.rs b/crates/router/src/core/payments/flows/verify_flow.rs index c18e25d467..1ecf2199b8 100644 --- a/crates/router/src/core/payments/flows/verify_flow.rs +++ b/crates/router/src/core/payments/flows/verify_flow.rs @@ -232,7 +232,7 @@ impl mandate::MandateBehaviour for types::VerifyRequestData { self.payment_method_data.clone() } - fn get_setup_mandate_details(&self) -> Option<&api_models::payments::MandateData> { + fn get_setup_mandate_details(&self) -> Option<&data_models::mandates::MandateData> { self.setup_mandate_details.as_ref() } } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index ed94e2a3d6..549f0d548c 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -5,8 +5,11 @@ use common_utils::{ ext_traits::{AsyncExt, ByteSliceExt, ValueExt}, fp_utils, generate_id, pii, }; -use data_models::payments::payment_intent::PaymentIntent; -use diesel_models::{enums, payment_attempt::PaymentAttempt}; +use data_models::{ + mandates::MandateData, + payments::{payment_attempt::PaymentAttempt, payment_intent::PaymentIntent}, +}; +use diesel_models::enums; // TODO : Evaluate all the helper functions () use error_stack::{report, IntoReport, ResultExt}; #[cfg(feature = "kms")] @@ -43,13 +46,13 @@ use crate::{ routes::{metrics, payment_methods, AppState}, services, types::{ - api::{self, admin, enums as api_enums, CustomerAcceptanceExt, MandateValidationFieldsExt}, + api::{self, admin, enums as api_enums, MandateValidationFieldsExt}, domain::{ self, types::{self, AsyncLift}, }, storage::{self, enums as storage_enums, ephemeral_key, CustomerUpdate::Update}, - transformers::ForeignTryFrom, + transformers::{ForeignFrom, ForeignTryFrom}, ErrorResponse, RouterData, }, utils::{ @@ -282,16 +285,14 @@ pub async fn get_token_pm_type_mandate_details( Option, Option, Option, - Option, + Option, Option, Option, )> { + let mandate_data = request.mandate_data.clone().map(MandateData::foreign_from); match mandate_type { Some(api::MandateTransactionType::NewMandateTransaction) => { - let setup_mandate = request - .mandate_data - .clone() - .get_required_value("mandate_data")?; + let setup_mandate = mandate_data.clone().get_required_value("mandate_data")?; Ok(( request.payment_token.to_owned(), request.payment_method, @@ -322,7 +323,7 @@ pub async fn get_token_pm_type_mandate_details( request.payment_token.to_owned(), request.payment_method, request.payment_method_type, - request.mandate_data.clone(), + mandate_data, None, None, )), @@ -662,7 +663,7 @@ pub fn create_redirect_url( format!( "{}/payments/{}/{}/redirect/response/{}", router_base_url, payment_attempt.payment_id, payment_attempt.merchant_id, connector_name, - ) + &creds_identifier_path + ) + creds_identifier_path.as_ref() } pub fn create_webhook_url( @@ -1935,7 +1936,7 @@ pub fn check_if_operation_confirm(operations: Op) -> bool { pub fn generate_mandate( merchant_id: String, connector: String, - setup_mandate_details: Option, + setup_mandate_details: Option, customer: &Option, payment_method_id: String, connector_mandate_id: Option, @@ -1976,13 +1977,13 @@ pub fn generate_mandate( Ok(Some( match data.mandate_type.get_required_value("mandate_type")? { - api::MandateType::SingleUse(data) => new_mandate + data_models::mandates::MandateDataType::SingleUse(data) => new_mandate .set_mandate_amount(Some(data.amount)) .set_mandate_currency(Some(data.currency)) .set_mandate_type(storage_enums::MandateType::SingleUse) .to_owned(), - api::MandateType::MultiUse(op_data) => match op_data { + data_models::mandates::MandateDataType::MultiUse(op_data) => match op_data { Some(data) => new_mandate .set_mandate_amount(Some(data.amount)) .set_mandate_currency(Some(data.currency)) diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index 804ef0f634..3c58864568 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -2,6 +2,7 @@ use std::marker::PhantomData; use api_models::enums::FrmSuggestion; use async_trait::async_trait; +use data_models::mandates::MandateData; use error_stack::ResultExt; use router_derive::PaymentOperation; use router_env::{instrument, tracing}; @@ -21,7 +22,6 @@ use crate::{ api::{self, PaymentIdTypeExt}, domain, storage::{self, enums as storage_enums}, - transformers::ForeignInto, }, utils::{self, OptionExt}, }; @@ -195,12 +195,11 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_intent.metadata = request.metadata.clone().or(payment_intent.metadata); // The operation merges mandate data from both request and payment_attempt - let setup_mandate = setup_mandate.map(|mandate_data| api_models::payments::MandateData { + let setup_mandate = setup_mandate.map(|mandate_data| MandateData { customer_acceptance: mandate_data.customer_acceptance, mandate_type: payment_attempt .mandate_details .clone() - .map(ForeignInto::foreign_into) .or(mandate_data.mandate_type), }); diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 3848199fba..b2f1c5bd25 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -19,7 +19,7 @@ use crate::{ types::{ api::{self, PaymentIdTypeExt}, domain, - storage::{self, enums, ConnectorResponseExt, PaymentAttemptExt}, + storage::{self, enums, payment_attempt::PaymentAttemptExt, ConnectorResponseExt}, }, utils::OptionExt, }; diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 5ad04c5df3..c06c8e4e77 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -21,7 +21,6 @@ use crate::{ api::{self, PaymentIdTypeExt}, domain, storage::{self, enums as storage_enums}, - transformers::ForeignInto, }, utils::{self, OptionExt}, }; @@ -194,14 +193,7 @@ impl GetTracker, api::PaymentsRequest> for Co payment_intent.metadata = request.metadata.clone().or(payment_intent.metadata); // The operation merges mandate data from both request and payment_attempt - let setup_mandate = setup_mandate.map(|mandate_data| api_models::payments::MandateData { - customer_acceptance: mandate_data.customer_acceptance, - mandate_type: payment_attempt - .mandate_details - .clone() - .map(ForeignInto::foreign_into) - .or(mandate_data.mandate_type), - }); + let setup_mandate = setup_mandate.map(Into::into); Ok(( Box::new(self), diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index bccfbd81c3..f8150283cb 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -13,7 +13,6 @@ use crate::{ core::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, payments::{self, helpers, operations, CustomerDetails, PaymentAddress, PaymentData}, - utils as core_utils, }, db::StorageInterface, routes::AppState, @@ -23,7 +22,6 @@ use crate::{ api::{self, PaymentIdTypeExt}, domain, storage::{self, enums as storage_enums}, - transformers::ForeignInto, }, utils::{self, OptionExt}, }; @@ -315,14 +313,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .or(payment_attempt.business_sub_label); // The operation merges mandate data from both request and payment_attempt - let setup_mandate = setup_mandate.map(|mandate_data| api_models::payments::MandateData { - customer_acceptance: mandate_data.customer_acceptance, - mandate_type: payment_attempt - .mandate_details - .clone() - .map(ForeignInto::foreign_into) - .or(mandate_data.mandate_type), - }); + let setup_mandate = setup_mandate.map(Into::into); Ok(( Box::new(self), @@ -627,7 +618,8 @@ impl ValidateRequest for PaymentConfir let mandate_type = helpers::validate_mandate(request, payments::is_operation_confirm(self))?; - let payment_id = core_utils::get_or_generate_id("payment_id", &given_payment_id, "pay")?; + let payment_id = + crate::core::utils::get_or_generate_id("payment_id", &given_payment_id, "pay")?; Ok(( Box::new(self), diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 68f37eaa57..6a3e4cb74e 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use api_models::enums::FrmSuggestion; use async_trait::async_trait; use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; +use data_models::mandates::MandateData; use diesel_models::ephemeral_key; use error_stack::{self, ResultExt}; use router_derive::PaymentOperation; @@ -27,7 +28,6 @@ use crate::{ self, enums::{self, IntentStatus}, }, - transformers::ForeignInto, }, utils::{self, OptionExt}, }; @@ -242,13 +242,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .transpose()?; // The operation merges mandate data from both request and payment_attempt - let setup_mandate = setup_mandate.map(|mandate_data| api_models::payments::MandateData { - customer_acceptance: mandate_data.customer_acceptance, - mandate_type: mandate_data.mandate_type.or(payment_attempt - .mandate_details - .clone() - .map(ForeignInto::foreign_into)), - }); + let setup_mandate: Option = setup_mandate.map(Into::into); Ok(( operation, @@ -576,7 +570,7 @@ impl PaymentCreate { mandate_details: request .mandate_data .as_ref() - .and_then(|inner| inner.mandate_type.clone().map(ForeignInto::foreign_into)), + .and_then(|inner| inner.mandate_type.clone().map(Into::into)), ..storage::PaymentAttemptNew::default() }) } diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs index 8641d3b1f8..2f260ee3e0 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -171,7 +171,7 @@ impl GetTracker, api::VerifyRequest> for Paym email: None, mandate_id: None, mandate_connector: None, - setup_mandate: request.mandate_data.clone(), + setup_mandate: request.mandate_data.clone().map(Into::into), token: request.payment_token.clone(), connector_response, payment_method_data: request.payment_method_data.clone(), diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 0f34839962..6d3c4d776a 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use api_models::enums::FrmSuggestion; use async_trait::async_trait; -use common_utils::{errors::ReportSwitchExt, ext_traits::AsyncExt}; +use common_utils::ext_traits::AsyncExt; use error_stack::ResultExt; use router_derive::PaymentOperation; use router_env::{instrument, tracing}; @@ -432,8 +432,7 @@ pub async fn get_payment_intent_payment_attempt( pi.active_attempt_id.as_str(), storage_scheme, ) - .await - .switch()?; + .await?; } api_models::payments::PaymentIdType::ConnectorTransactionId(ref id) => { pa = db @@ -442,8 +441,7 @@ pub async fn get_payment_intent_payment_attempt( id, storage_scheme, ) - .await - .switch()?; + .await?; pi = db .find_payment_intent_by_payment_id_merchant_id( pa.payment_id.as_str(), @@ -455,8 +453,7 @@ pub async fn get_payment_intent_payment_attempt( api_models::payments::PaymentIdType::PaymentAttemptId(ref id) => { pa = db .find_payment_attempt_by_attempt_id_merchant_id(id, merchant_id, storage_scheme) - .await - .switch()?; + .await?; pi = db .find_payment_intent_by_payment_id_merchant_id( pa.payment_id.as_str(), @@ -472,8 +469,7 @@ pub async fn get_payment_intent_payment_attempt( merchant_id, storage_scheme, ) - .await - .switch()?; + .await?; pi = db .find_payment_intent_by_payment_id_merchant_id( diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index dbd61d274d..647979a118 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -21,7 +21,6 @@ use crate::{ api::{self, PaymentIdTypeExt}, domain, storage::{self, enums as storage_enums}, - transformers::ForeignInto, }, utils::OptionExt, }; @@ -304,13 +303,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .transpose()?; // The operation merges mandate data from both request and payment_attempt - let setup_mandate = setup_mandate.map(|mandate_data| api_models::payments::MandateData { - customer_acceptance: mandate_data.customer_acceptance, - mandate_type: mandate_data.mandate_type.or(payment_attempt - .mandate_details - .clone() - .map(ForeignInto::foreign_into)), - }); + let setup_mandate = setup_mandate.map(Into::into); Ok(( next_operation, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 680698e9bd..25c9fb2c84 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2,7 +2,8 @@ use std::{fmt::Debug, marker::PhantomData, str::FromStr}; use api_models::payments::FrmMessage; use common_utils::fp_utils; -use diesel_models::{ephemeral_key, payment_attempt::PaymentListFilters}; +use data_models::mandates::MandateData; +use diesel_models::ephemeral_key; use error_stack::{IntoReport, ResultExt}; use router_env::{instrument, tracing}; @@ -344,7 +345,7 @@ pub fn payments_to_payments_response( ephemeral_key_option: Option, session_tokens: Vec, fraud_check: Option, - mandate_data: Option, + mandate_data: Option, connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, connector_http_status_code: Option, ) -> RouterResponse @@ -551,7 +552,50 @@ where ) .set_mandate_id(mandate_id) .set_mandate_data( - mandate_data.map(api::MandateData::from), + mandate_data.map(|d| api::MandateData { + customer_acceptance: d.customer_acceptance.map(|d| { + api::CustomerAcceptance { + acceptance_type: match d.acceptance_type { + data_models::mandates::AcceptanceType::Online => { + api::AcceptanceType::Online + } + data_models::mandates::AcceptanceType::Offline => { + api::AcceptanceType::Offline + } + }, + accepted_at: d.accepted_at, + online: d.online.map(|d| api::OnlineMandate { + ip_address: d.ip_address, + user_agent: d.user_agent, + }), + } + }), + mandate_type: d.mandate_type.map(|d| match d { + data_models::mandates::MandateDataType::MultiUse(Some(i)) => { + api::MandateType::MultiUse(Some(api::MandateAmountData { + amount: i.amount, + currency: i.currency, + start_date: i.start_date, + end_date: i.end_date, + metadata: i.metadata, + })) + } + data_models::mandates::MandateDataType::SingleUse(i) => { + api::MandateType::SingleUse( + api::payments::MandateAmountData { + amount: i.amount, + currency: i.currency, + start_date: i.start_date, + end_date: i.end_date, + metadata: i.metadata, + }, + ) + } + data_models::mandates::MandateDataType::MultiUse(None) => { + api::MandateType::MultiUse(None) + } + }), + }), auth_flow == services::AuthFlow::Merchant, ) .set_description(payment_intent.description) @@ -777,17 +821,6 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay } } -impl ForeignFrom for api_models::payments::PaymentListFilters { - fn foreign_from(item: PaymentListFilters) -> Self { - Self { - connector: item.connector, - currency: item.currency, - status: item.status, - payment_method: item.payment_method, - } - } -} - impl ForeignFrom for api::ephemeral_key::EphemeralKeyCreateResponse { fn foreign_from(from: ephemeral_key::EphemeralKey) -> Self { Self { diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index ec5d138639..88e8e59ed3 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -144,7 +144,7 @@ pub fn validate_refund_list(limit: Option) -> CustomResult RouterResult<()> { let payment_method = payment_attempt diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 13878b4741..14a3b77714 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -903,7 +903,7 @@ pub fn is_merchant_enabled_for_payment_id_as_connector_request_id( pub fn get_connector_request_reference_id( conf: &settings::Settings, merchant_id: &str, - payment_attempt: &diesel_models::payment_attempt::PaymentAttempt, + payment_attempt: &data_models::payments::payment_attempt::PaymentAttempt, ) -> String { let is_config_enabled_for_merchant = is_merchant_enabled_for_payment_id_as_connector_request_id(conf, merchant_id); diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index bc1fa3f914..adf1f2fef8 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -241,7 +241,8 @@ pub async fn get_payment_attempt_from_object_reference_id( state: &AppState, object_reference_id: api_models::webhooks::ObjectReferenceId, merchant_account: &domain::MerchantAccount, -) -> CustomResult { +) -> CustomResult +{ let db = &*state.store; match object_reference_id { api::ObjectReferenceId::PaymentId(api::PaymentIdType::ConnectorTransactionId(ref id)) => db @@ -279,7 +280,7 @@ pub async fn get_or_update_dispute_object( option_dispute: Option, dispute_details: api::disputes::DisputePayload, merchant_id: &str, - payment_attempt: &diesel_models::payment_attempt::PaymentAttempt, + payment_attempt: &data_models::payments::payment_attempt::PaymentAttempt, event_type: api_models::webhooks::IncomingWebhookEvent, connector_name: &str, ) -> CustomResult { diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index eeeba2b756..389229742f 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -17,14 +17,15 @@ pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; pub mod merchant_key_store; -pub mod payment_attempt; pub mod payment_method; pub mod payout_attempt; pub mod payouts; pub mod refund; pub mod reverse_lookup; -use data_models::payments::payment_intent::PaymentIntentInterface; +use data_models::payments::{ + payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface, +}; use masking::PeekInterface; use storage_impl::{redis::kv_store::RedisConnInterface, MockDb}; @@ -58,7 +59,7 @@ pub trait StorageInterface: + merchant_account::MerchantAccountInterface + merchant_connector_account::ConnectorAccessToken + merchant_connector_account::MerchantConnectorAccountInterface - + payment_attempt::PaymentAttemptInterface + + PaymentAttemptInterface + PaymentIntentInterface + payment_method::PaymentMethodInterface + scheduler::SchedulerInterface diff --git a/crates/router/src/db/api_keys.rs b/crates/router/src/db/api_keys.rs index 19d5e73d1c..4ba9e47e9a 100644 --- a/crates/router/src/db/api_keys.rs +++ b/crates/router/src/db/api_keys.rs @@ -390,7 +390,10 @@ mod tests { #[allow(clippy::unwrap_used)] #[tokio::test] async fn test_mockdb_api_key_interface() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let key1 = mockdb .insert_api_key(storage::ApiKeyNew { @@ -473,7 +476,10 @@ mod tests { #[allow(clippy::unwrap_used)] #[tokio::test] async fn test_api_keys_cache() { - let db = MockDb::new().await; + #[allow(clippy::expect_used)] + let db = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let redis_conn = db.get_redis_conn().unwrap(); redis_conn diff --git a/crates/router/src/db/dispute.rs b/crates/router/src/db/dispute.rs index b6c102693c..71f47872c0 100644 --- a/crates/router/src/db/dispute.rs +++ b/crates/router/src/db/dispute.rs @@ -371,6 +371,7 @@ mod tests { enums::{DisputeStage, DisputeStatus}, }; use masking::Secret; + use redis_interface::RedisSettings; use serde_json::Value; use time::macros::datetime; @@ -409,7 +410,10 @@ mod tests { #[tokio::test] async fn test_insert_dispute() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&RedisSettings::default()) + .await + .expect("Failed to create a mock DB"); let created_dispute = mockdb .insert_dispute(create_dispute_new(DisputeNewIds { @@ -437,7 +441,10 @@ mod tests { #[tokio::test] async fn test_find_by_merchant_id_payment_id_connector_dispute_id() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_dispute = mockdb .insert_dispute(create_dispute_new(DisputeNewIds { @@ -477,7 +484,10 @@ mod tests { #[tokio::test] async fn test_find_dispute_by_merchant_id_dispute_id() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_dispute = mockdb .insert_dispute(create_dispute_new(DisputeNewIds { @@ -511,7 +521,10 @@ mod tests { #[tokio::test] async fn test_find_disputes_by_merchant_id() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_dispute = mockdb .insert_dispute(create_dispute_new(DisputeNewIds { @@ -561,7 +574,10 @@ mod tests { #[tokio::test] async fn test_find_disputes_by_merchant_id_payment_id() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_dispute = mockdb .insert_dispute(create_dispute_new(DisputeNewIds { @@ -614,7 +630,10 @@ mod tests { #[tokio::test] async fn test_update_dispute_update() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_dispute = mockdb .insert_dispute(create_dispute_new(DisputeNewIds { @@ -691,7 +710,10 @@ mod tests { #[tokio::test] async fn test_update_dispute_update_status() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_dispute = mockdb .insert_dispute(create_dispute_new(DisputeNewIds { @@ -763,7 +785,10 @@ mod tests { #[tokio::test] async fn test_update_dispute_update_evidence() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_dispute = mockdb .insert_dispute(create_dispute_new(DisputeNewIds { diff --git a/crates/router/src/db/events.rs b/crates/router/src/db/events.rs index 0e88d53108..3d87fc70ea 100644 --- a/crates/router/src/db/events.rs +++ b/crates/router/src/db/events.rs @@ -108,7 +108,10 @@ mod tests { #[allow(clippy::unwrap_used)] #[tokio::test] async fn test_mockdb_event_interface() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let event1 = mockdb .insert_event(storage::EventNew { diff --git a/crates/router/src/db/locker_mock_up.rs b/crates/router/src/db/locker_mock_up.rs index b47484c610..ca3028486b 100644 --- a/crates/router/src/db/locker_mock_up.rs +++ b/crates/router/src/db/locker_mock_up.rs @@ -163,7 +163,10 @@ mod tests { #[tokio::test] async fn find_locker_by_card_id() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_locker = mockdb .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds { @@ -191,7 +194,10 @@ mod tests { #[tokio::test] async fn insert_locker_mock_up() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_locker = mockdb .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds { @@ -218,7 +224,10 @@ mod tests { #[tokio::test] async fn delete_locker_mock_up() { - let mockdb = MockDb::new().await; + #[allow(clippy::expect_used)] + let mockdb = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let created_locker = mockdb .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds { diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 501a71bb9f..954dead026 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -772,7 +772,7 @@ mod merchant_connector_account_cache_tests { }, services, types::{ - domain::{self, behaviour::Conversion, types as domain_types}, + domain::{self, behaviour::Conversion}, storage, }, }; @@ -780,7 +780,10 @@ mod merchant_connector_account_cache_tests { #[allow(clippy::unwrap_used)] #[tokio::test] async fn test_connector_profile_id_cache() { - let db = MockDb::new().await; + #[allow(clippy::expect_used)] + let db = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create Mock store"); let redis_conn = db.get_redis_conn().unwrap(); let master_key = db.get_master_key(); @@ -797,7 +800,7 @@ mod merchant_connector_account_cache_tests { db.insert_merchant_key_store( domain::MerchantKeyStore { merchant_id: merchant_id.into(), - key: domain_types::encrypt( + key: domain::types::encrypt( services::generate_aes256_key().unwrap().to_vec().into(), master_key, ) @@ -819,7 +822,7 @@ mod merchant_connector_account_cache_tests { id: Some(1), merchant_id: merchant_id.to_string(), connector_name: "stripe".to_string(), - connector_account_details: domain_types::encrypt( + connector_account_details: domain::types::encrypt( serde_json::Value::default().into(), merchant_key.key.get_inner().peek(), ) @@ -847,15 +850,16 @@ mod merchant_connector_account_cache_tests { .unwrap(); let find_call = || async { - db.find_merchant_connector_account_by_profile_id_connector_name( - profile_id, - &mca.connector_name, - &merchant_key, + Conversion::convert( + db.find_merchant_connector_account_by_profile_id_connector_name( + profile_id, + &mca.connector_name, + &merchant_key, + ) + .await + .unwrap(), ) .await - .unwrap() - .convert() - .await .change_context(errors::StorageError::DecryptionError) }; let _: storage::MerchantConnectorAccount = cache::get_or_populate_in_memory( diff --git a/crates/router/src/db/merchant_key_store.rs b/crates/router/src/db/merchant_key_store.rs index 28d0a71a4b..8cb93fc369 100644 --- a/crates/router/src/db/merchant_key_store.rs +++ b/crates/router/src/db/merchant_key_store.rs @@ -149,13 +149,16 @@ mod tests { use crate::{ db::{merchant_key_store::MerchantKeyStoreInterface, MasterKeyInterface, MockDb}, services, - types::domain::{self, types as domain_types}, + types::domain::{self}, }; #[allow(clippy::unwrap_used)] #[tokio::test] async fn test_mock_db_merchant_key_store_interface() { - let mock_db = MockDb::new().await; + #[allow(clippy::expect_used)] + let mock_db = MockDb::new(&redis_interface::RedisSettings::default()) + .await + .expect("Failed to create mock DB"); let master_key = mock_db.get_master_key(); let merchant_id = "merchant1"; @@ -163,7 +166,7 @@ mod tests { .insert_merchant_key_store( domain::MerchantKeyStore { merchant_id: merchant_id.into(), - key: domain_types::encrypt( + key: domain::types::encrypt( services::generate_aes256_key().unwrap().to_vec().into(), master_key, ) @@ -188,7 +191,7 @@ mod tests { .insert_merchant_key_store( domain::MerchantKeyStore { merchant_id: merchant_id.into(), - key: domain_types::encrypt( + key: domain::types::encrypt( services::generate_aes256_key().unwrap().to_vec().into(), master_key, ) diff --git a/crates/router/src/db/payment_attempt.rs b/crates/router/src/db/payment_attempt.rs deleted file mode 100644 index d8b1e20db2..0000000000 --- a/crates/router/src/db/payment_attempt.rs +++ /dev/null @@ -1,1051 +0,0 @@ -use api_models::enums::{Connector, PaymentMethod}; - -use super::MockDb; -use crate::{ - core::errors::{self, CustomResult}, - types::storage::{self as types, enums}, -}; - -#[async_trait::async_trait] -pub trait PaymentAttemptInterface { - async fn insert_payment_attempt( - &self, - payment_attempt: types::PaymentAttemptNew, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn update_payment_attempt_with_attempt_id( - &self, - this: types::PaymentAttempt, - payment_attempt: types::PaymentAttemptUpdate, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( - &self, - connector_transaction_id: &str, - payment_id: &str, - merchant_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( - &self, - payment_id: &str, - merchant_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn find_payment_attempt_by_merchant_id_connector_txn_id( - &self, - merchant_id: &str, - connector_txn_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( - &self, - payment_id: &str, - merchant_id: &str, - attempt_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn find_payment_attempt_by_attempt_id_merchant_id( - &self, - attempt_id: &str, - merchant_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn find_payment_attempt_by_preprocessing_id_merchant_id( - &self, - preprocessing_id: &str, - merchant_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn find_attempts_by_merchant_id_payment_id( - &self, - merchant_id: &str, - payment_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult, errors::StorageError>; - - async fn get_filters_for_payments( - &self, - pi: &[types::PaymentIntent], - merchant_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; - - async fn get_total_count_of_filtered_payment_attempts( - &self, - merchant_id: &str, - active_attempt_ids: &[String], - connector: Option>, - payment_methods: Option>, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult; -} - -#[cfg(not(feature = "kv_store"))] -mod storage { - use api_models::enums::{Connector, PaymentMethod}; - use error_stack::IntoReport; - use storage_impl::DataModelExt; - - use super::PaymentAttemptInterface; - use crate::{ - connection, - core::errors::{self, CustomResult}, - services::Store, - types::storage::{enums, payment_attempt::*}, - }; - - #[async_trait::async_trait] - impl PaymentAttemptInterface for Store { - async fn insert_payment_attempt( - &self, - payment_attempt: PaymentAttemptNew, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; - payment_attempt - .insert(&conn) - .await - .map_err(Into::into) - .into_report() - } - - async fn update_payment_attempt_with_attempt_id( - &self, - this: PaymentAttempt, - payment_attempt: PaymentAttemptUpdate, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; - this.update_with_attempt_id(&conn, payment_attempt) - .await - .map_err(Into::into) - .into_report() - } - - async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( - &self, - connector_transaction_id: &str, - payment_id: &str, - merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_connector_transaction_id_payment_id_merchant_id( - &conn, - connector_transaction_id, - payment_id, - merchant_id, - ) - .await - .map_err(Into::into) - .into_report() - } - - async fn find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( - &self, - payment_id: &str, - merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_last_successful_attempt_by_payment_id_merchant_id( - &conn, - payment_id, - merchant_id, - ) - .await - .map_err(Into::into) - .into_report() - } - - async fn find_payment_attempt_by_merchant_id_connector_txn_id( - &self, - merchant_id: &str, - connector_txn_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_merchant_id_connector_txn_id( - &conn, - merchant_id, - connector_txn_id, - ) - .await - .map_err(Into::into) - .into_report() - } - - async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( - &self, - payment_id: &str, - merchant_id: &str, - attempt_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - - PaymentAttempt::find_by_payment_id_merchant_id_attempt_id( - &conn, - payment_id, - merchant_id, - attempt_id, - ) - .await - .map_err(Into::into) - .into_report() - } - - async fn get_filters_for_payments( - &self, - pi: &[data_models::payments::payment_intent::PaymentIntent], - merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult - { - let conn = connection::pg_connection_read(self).await?; - let intents = pi - .iter() - .cloned() - .map(|pi| pi.to_storage_model()) - .collect::>(); - PaymentAttempt::get_filters_for_payments(&conn, intents.as_slice(), merchant_id) - .await - .map_err(Into::into) - .into_report() - } - - async fn get_total_count_of_filtered_payment_attempts( - &self, - merchant_id: &str, - active_attempt_ids: &[String], - connector: Option>, - payment_methods: Option>, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - let connector_strings = if let Some(connector_vec) = &connector { - Some( - connector_vec - .iter() - .map(|c| c.to_string()) - .collect::>(), - ) - } else { - None - }; - PaymentAttempt::get_total_count_of_attempts( - &conn, - merchant_id, - active_attempt_ids, - connector_strings, - payment_methods, - ) - .await - .map_err(Into::into) - .into_report() - } - - async fn find_payment_attempt_by_preprocessing_id_merchant_id( - &self, - preprocessing_id: &str, - merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - - PaymentAttempt::find_by_merchant_id_preprocessing_id( - &conn, - merchant_id, - preprocessing_id, - ) - .await - .map_err(Into::into) - .into_report() - } - - async fn find_attempts_by_merchant_id_payment_id( - &self, - merchant_id: &str, - payment_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult, errors::StorageError> { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_merchant_id_payment_id(&conn, merchant_id, payment_id) - .await - .map_err(Into::into) - .into_report() - } - - async fn find_payment_attempt_by_attempt_id_merchant_id( - &self, - merchant_id: &str, - attempt_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - - PaymentAttempt::find_by_merchant_id_attempt_id(&conn, merchant_id, attempt_id) - .await - .map_err(Into::into) - .into_report() - } - } -} - -#[async_trait::async_trait] -impl PaymentAttemptInterface for MockDb { - async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( - &self, - _payment_id: &str, - _merchant_id: &str, - _attempt_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - // [#172]: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? - } - - async fn get_filters_for_payments( - &self, - _pi: &[data_models::payments::payment_intent::PaymentIntent], - _merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult - { - Err(errors::StorageError::MockDbError)? - } - - async fn get_total_count_of_filtered_payment_attempts( - &self, - _merchant_id: &str, - _active_attempt_ids: &[String], - _connector: Option>, - _payment_methods: Option>, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - Err(errors::StorageError::MockDbError)? - } - - async fn find_payment_attempt_by_attempt_id_merchant_id( - &self, - _attempt_id: &str, - _merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - // [#172]: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? - } - - async fn find_payment_attempt_by_preprocessing_id_merchant_id( - &self, - _preprocessing_id: &str, - _merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - // [#172]: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? - } - - async fn find_payment_attempt_by_merchant_id_connector_txn_id( - &self, - _merchant_id: &str, - _connector_txn_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - // [#172]: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? - } - - async fn find_attempts_by_merchant_id_payment_id( - &self, - _merchant_id: &str, - _payment_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult, errors::StorageError> { - // [#172]: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? - } - - #[allow(clippy::panic)] - async fn insert_payment_attempt( - &self, - payment_attempt: types::PaymentAttemptNew, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let mut payment_attempts = self.payment_attempts.lock().await; - #[allow(clippy::as_conversions)] - let id = payment_attempts.len() as i32; - let time = common_utils::date_time::now(); - - let payment_attempt = types::PaymentAttempt { - id, - payment_id: payment_attempt.payment_id, - merchant_id: payment_attempt.merchant_id, - attempt_id: payment_attempt.attempt_id, - status: payment_attempt.status, - amount: payment_attempt.amount, - currency: payment_attempt.currency, - save_to_locker: payment_attempt.save_to_locker, - connector: payment_attempt.connector, - error_message: payment_attempt.error_message, - offer_amount: payment_attempt.offer_amount, - surcharge_amount: payment_attempt.surcharge_amount, - tax_amount: payment_attempt.tax_amount, - payment_method_id: payment_attempt.payment_method_id, - payment_method: payment_attempt.payment_method, - connector_transaction_id: None, - capture_method: payment_attempt.capture_method, - capture_on: payment_attempt.capture_on, - confirm: payment_attempt.confirm, - authentication_type: payment_attempt.authentication_type, - created_at: payment_attempt.created_at.unwrap_or(time), - modified_at: payment_attempt.modified_at.unwrap_or(time), - last_synced: payment_attempt.last_synced, - cancellation_reason: payment_attempt.cancellation_reason, - amount_to_capture: payment_attempt.amount_to_capture, - mandate_id: None, - browser_info: None, - payment_token: None, - error_code: payment_attempt.error_code, - connector_metadata: None, - payment_experience: payment_attempt.payment_experience, - payment_method_type: payment_attempt.payment_method_type, - payment_method_data: payment_attempt.payment_method_data, - business_sub_label: payment_attempt.business_sub_label, - straight_through_algorithm: payment_attempt.straight_through_algorithm, - mandate_details: payment_attempt.mandate_details, - preprocessing_step_id: payment_attempt.preprocessing_step_id, - error_reason: payment_attempt.error_reason, - multiple_capture_count: payment_attempt.multiple_capture_count, - connector_response_reference_id: None, - }; - payment_attempts.push(payment_attempt.clone()); - Ok(payment_attempt) - } - - // safety: only used for testing - #[allow(clippy::unwrap_used)] - async fn update_payment_attempt_with_attempt_id( - &self, - this: types::PaymentAttempt, - payment_attempt: types::PaymentAttemptUpdate, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let mut payment_attempts = self.payment_attempts.lock().await; - - let item = payment_attempts - .iter_mut() - .find(|item| item.attempt_id == this.attempt_id) - .unwrap(); - - *item = payment_attempt.apply_changeset(this); - - Ok(item.clone()) - } - - async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( - &self, - _connector_transaction_id: &str, - _payment_id: &str, - _merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - // [#172]: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? - } - - // safety: only used for testing - #[allow(clippy::unwrap_used)] - async fn find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( - &self, - payment_id: &str, - merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let payment_attempts = self.payment_attempts.lock().await; - - Ok(payment_attempts - .iter() - .find(|payment_attempt| { - payment_attempt.payment_id == payment_id - && payment_attempt.merchant_id == merchant_id - }) - .cloned() - .unwrap()) - } -} - -#[cfg(feature = "kv_store")] -mod storage { - use common_utils::date_time; - use diesel_models::reverse_lookup::ReverseLookup; - use error_stack::{IntoReport, ResultExt}; - use redis_interface::HsetnxReply; - use storage_impl::{redis::kv_store::RedisConnInterface, DataModelExt}; - - use super::PaymentAttemptInterface; - use crate::{ - connection, - core::errors::{self, CustomResult}, - db::reverse_lookup::ReverseLookupInterface, - services::Store, - types::storage::{enums, kv, payment_attempt::*, ReverseLookupNew}, - utils::db_utils, - }; - - #[async_trait::async_trait] - impl PaymentAttemptInterface for Store { - async fn insert_payment_attempt( - &self, - payment_attempt: PaymentAttemptNew, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - match storage_scheme { - enums::MerchantStorageScheme::PostgresOnly => { - let conn = connection::pg_connection_write(self).await?; - payment_attempt - .insert(&conn) - .await - .map_err(Into::into) - .into_report() - } - - enums::MerchantStorageScheme::RedisKv => { - let key = format!( - "{}_{}", - payment_attempt.merchant_id, payment_attempt.payment_id - ); - - let created_attempt = PaymentAttempt { - id: Default::default(), - payment_id: payment_attempt.payment_id.clone(), - merchant_id: payment_attempt.merchant_id.clone(), - attempt_id: payment_attempt.attempt_id.clone(), - status: payment_attempt.status, - amount: payment_attempt.amount, - currency: payment_attempt.currency, - save_to_locker: payment_attempt.save_to_locker, - connector: payment_attempt.connector.clone(), - error_message: payment_attempt.error_message.clone(), - offer_amount: payment_attempt.offer_amount, - surcharge_amount: payment_attempt.surcharge_amount, - tax_amount: payment_attempt.tax_amount, - payment_method_id: payment_attempt.payment_method_id.clone(), - payment_method: payment_attempt.payment_method, - connector_transaction_id: None, - capture_method: payment_attempt.capture_method, - capture_on: payment_attempt.capture_on, - confirm: payment_attempt.confirm, - authentication_type: payment_attempt.authentication_type, - created_at: payment_attempt.created_at.unwrap_or_else(date_time::now), - modified_at: payment_attempt.created_at.unwrap_or_else(date_time::now), - last_synced: payment_attempt.last_synced, - amount_to_capture: payment_attempt.amount_to_capture, - cancellation_reason: payment_attempt.cancellation_reason.clone(), - mandate_id: payment_attempt.mandate_id.clone(), - browser_info: payment_attempt.browser_info.clone(), - payment_token: payment_attempt.payment_token.clone(), - error_code: payment_attempt.error_code.clone(), - connector_metadata: payment_attempt.connector_metadata.clone(), - payment_experience: payment_attempt.payment_experience, - payment_method_type: payment_attempt.payment_method_type, - payment_method_data: payment_attempt.payment_method_data.clone(), - business_sub_label: payment_attempt.business_sub_label.clone(), - straight_through_algorithm: payment_attempt - .straight_through_algorithm - .clone(), - mandate_details: payment_attempt.mandate_details.clone(), - preprocessing_step_id: payment_attempt.preprocessing_step_id.clone(), - error_reason: payment_attempt.error_reason.clone(), - multiple_capture_count: payment_attempt.multiple_capture_count, - connector_response_reference_id: None, - }; - - let field = format!("pa_{}", created_attempt.attempt_id); - match self - .get_redis_conn() - .map_err(Into::::into)? - .serialize_and_set_hash_field_if_not_exist(&key, &field, &created_attempt) - .await - { - Ok(HsetnxReply::KeyNotSet) => Err(errors::StorageError::DuplicateValue { - entity: "payment attempt", - key: Some(key), - }) - .into_report(), - Ok(HsetnxReply::KeySet) => { - let conn = connection::pg_connection_write(self).await?; - - //Reverse lookup for attempt_id - ReverseLookupNew { - lookup_id: format!( - "{}_{}", - &created_attempt.merchant_id, &created_attempt.attempt_id, - ), - pk_id: key, - sk_id: field, - source: "payment_attempt".to_string(), - } - .insert(&conn) - .await - .map_err(Into::::into) - .into_report()?; - - let redis_entry = kv::TypedSql { - op: kv::DBOperation::Insert { - insertable: kv::Insertable::PaymentAttempt(payment_attempt), - }, - }; - self.push_to_drainer_stream::( - redis_entry, - crate::utils::storage_partitioning::PartitionKey::MerchantIdPaymentId { - merchant_id: &created_attempt.merchant_id, - payment_id: &created_attempt.payment_id, - } - ) - .await.change_context(errors::StorageError::KVError)?; - Ok(created_attempt) - } - Err(error) => Err(error.change_context(errors::StorageError::KVError)), - } - } - } - } - - async fn update_payment_attempt_with_attempt_id( - &self, - this: PaymentAttempt, - payment_attempt: PaymentAttemptUpdate, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - match storage_scheme { - enums::MerchantStorageScheme::PostgresOnly => { - let conn = connection::pg_connection_write(self).await?; - this.update_with_attempt_id(&conn, payment_attempt) - .await - .map_err(Into::into) - .into_report() - } - - enums::MerchantStorageScheme::RedisKv => { - let key = format!("{}_{}", this.merchant_id, this.payment_id); - let old_connector_transaction_id = &this.connector_transaction_id; - let old_preprocessing_id = &this.preprocessing_step_id; - let updated_attempt = payment_attempt.clone().apply_changeset(this.clone()); - // Check for database presence as well Maybe use a read replica here ? - let redis_value = serde_json::to_string(&updated_attempt) - .into_report() - .change_context(errors::StorageError::KVError)?; - let field = format!("pa_{}", updated_attempt.attempt_id); - let updated_attempt = self - .get_redis_conn() - .change_context(errors::StorageError::KVError)? - .set_hash_fields(&key, (&field, &redis_value)) - .await - .map(|_| updated_attempt) - .change_context(errors::StorageError::KVError)?; - - match ( - old_connector_transaction_id, - &updated_attempt.connector_transaction_id, - ) { - (None, Some(connector_transaction_id)) => { - add_connector_txn_id_to_reverse_lookup( - self, - key.as_str(), - this.merchant_id.as_str(), - updated_attempt.attempt_id.as_str(), - connector_transaction_id.as_str(), - ) - .await?; - } - (Some(old_connector_transaction_id), Some(connector_transaction_id)) => { - if old_connector_transaction_id.ne(connector_transaction_id) { - add_connector_txn_id_to_reverse_lookup( - self, - key.as_str(), - this.merchant_id.as_str(), - updated_attempt.attempt_id.as_str(), - connector_transaction_id.as_str(), - ) - .await?; - } - } - (_, _) => {} - } - - match (old_preprocessing_id, &updated_attempt.preprocessing_step_id) { - (None, Some(preprocessing_id)) => { - add_preprocessing_id_to_reverse_lookup( - self, - key.as_str(), - this.merchant_id.as_str(), - updated_attempt.attempt_id.as_str(), - preprocessing_id.as_str(), - ) - .await?; - } - (Some(old_preprocessing_id), Some(preprocessing_id)) => { - if old_preprocessing_id.ne(preprocessing_id) { - add_preprocessing_id_to_reverse_lookup( - self, - key.as_str(), - this.merchant_id.as_str(), - updated_attempt.attempt_id.as_str(), - preprocessing_id.as_str(), - ) - .await?; - } - } - (_, _) => {} - } - - let redis_entry = kv::TypedSql { - op: kv::DBOperation::Update { - updatable: kv::Updateable::PaymentAttemptUpdate( - kv::PaymentAttemptUpdateMems { - orig: this, - update_data: payment_attempt, - }, - ), - }, - }; - self.push_to_drainer_stream::( - redis_entry, - crate::utils::storage_partitioning::PartitionKey::MerchantIdPaymentId { - merchant_id: &updated_attempt.merchant_id, - payment_id: &updated_attempt.payment_id, - }, - ) - .await - .change_context(errors::StorageError::KVError)?; - Ok(updated_attempt) - } - } - } - - async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( - &self, - connector_transaction_id: &str, - payment_id: &str, - merchant_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let database_call = || async { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_connector_transaction_id_payment_id_merchant_id( - &conn, - connector_transaction_id, - payment_id, - merchant_id, - ) - .await - .map_err(Into::into) - .into_report() - }; - match storage_scheme { - enums::MerchantStorageScheme::PostgresOnly => database_call().await, - enums::MerchantStorageScheme::RedisKv => { - // We assume that PaymentAttempt <=> PaymentIntent is a one-to-one relation for now - let lookup_id = format!("{merchant_id}_{connector_transaction_id}"); - let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; - let key = &lookup.pk_id; - - db_utils::try_redis_get_else_try_database_get( - self.get_redis_conn() - .map_err(Into::::into)? - .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), - database_call, - ) - .await - } - } - } - - async fn find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( - &self, - payment_id: &str, - merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_last_successful_attempt_by_payment_id_merchant_id( - &conn, - payment_id, - merchant_id, - ) - .await - .map_err(Into::into) - .into_report() - } - - async fn find_payment_attempt_by_merchant_id_connector_txn_id( - &self, - merchant_id: &str, - connector_txn_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let database_call = || async { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_merchant_id_connector_txn_id( - &conn, - merchant_id, - connector_txn_id, - ) - .await - .map_err(Into::into) - .into_report() - }; - match storage_scheme { - enums::MerchantStorageScheme::PostgresOnly => database_call().await, - - enums::MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{connector_txn_id}"); - let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; - - let key = &lookup.pk_id; - db_utils::try_redis_get_else_try_database_get( - self.get_redis_conn() - .map_err(Into::::into)? - .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), - database_call, - ) - .await - } - } - } - - async fn find_payment_attempt_by_attempt_id_merchant_id( - &self, - attempt_id: &str, - merchant_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let database_call = || async { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_merchant_id_attempt_id(&conn, merchant_id, attempt_id) - .await - .map_err(Into::into) - .into_report() - }; - match storage_scheme { - enums::MerchantStorageScheme::PostgresOnly => database_call().await, - - enums::MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{attempt_id}"); - let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; - let key = &lookup.pk_id; - db_utils::try_redis_get_else_try_database_get( - self.get_redis_conn() - .map_err(Into::::into)? - .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), - database_call, - ) - .await - } - } - } - - async fn find_payment_attempt_by_preprocessing_id_merchant_id( - &self, - preprocessing_id: &str, - merchant_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let database_call = || async { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_merchant_id_preprocessing_id( - &conn, - merchant_id, - preprocessing_id, - ) - .await - .map_err(Into::into) - .into_report() - }; - match storage_scheme { - enums::MerchantStorageScheme::PostgresOnly => database_call().await, - enums::MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{preprocessing_id}"); - let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; - let key = &lookup.pk_id; - - db_utils::try_redis_get_else_try_database_get( - self.get_redis_conn() - .map_err(Into::::into)? - .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), - database_call, - ) - .await - } - } - } - - async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( - &self, - payment_id: &str, - merchant_id: &str, - attempt_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let database_call = || async { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_payment_id_merchant_id_attempt_id( - &conn, - payment_id, - merchant_id, - attempt_id, - ) - .await - .map_err(Into::into) - .into_report() - }; - match storage_scheme { - enums::MerchantStorageScheme::PostgresOnly => database_call().await, - - enums::MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{attempt_id}"); - let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; - let key = &lookup.pk_id; - db_utils::try_redis_get_else_try_database_get( - self.get_redis_conn() - .map_err(Into::::into)? - .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), - database_call, - ) - .await - } - } - } - - async fn find_attempts_by_merchant_id_payment_id( - &self, - merchant_id: &str, - payment_id: &str, - storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult, errors::StorageError> { - match storage_scheme { - enums::MerchantStorageScheme::PostgresOnly => { - let conn = connection::pg_connection_read(self).await?; - PaymentAttempt::find_by_merchant_id_payment_id(&conn, merchant_id, payment_id) - .await - .map_err(Into::into) - .into_report() - } - enums::MerchantStorageScheme::RedisKv => { - let key = format!("{merchant_id}_{payment_id}"); - let lookup = self.get_lookup_by_lookup_id(&key).await?; - - let pattern = db_utils::generate_hscan_pattern_for_attempt(&lookup.sk_id); - - self.get_redis_conn() - .map_err(Into::::into)? - .hscan_and_deserialize(&key, &pattern, None) - .await - .change_context(errors::StorageError::KVError) - } - } - } - - async fn get_filters_for_payments( - &self, - pi: &[data_models::payments::payment_intent::PaymentIntent], - merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult - { - let conn = connection::pg_connection_read(self).await?; - let intents = pi - .iter() - .cloned() - .map(|pi| pi.to_storage_model()) - .collect::>(); - PaymentAttempt::get_filters_for_payments(&conn, intents.as_slice(), merchant_id) - .await - .map_err(Into::into) - .into_report() - } - - async fn get_total_count_of_filtered_payment_attempts( - &self, - merchant_id: &str, - active_attempt_ids: &[String], - connector: Option>, - payment_methods: Option>, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - - let connector_strings = connector.as_ref().map(|connector_vec| { - connector_vec - .iter() - .map(|c| c.to_string()) - .collect::>() - }); - - PaymentAttempt::get_total_count_of_attempts( - &conn, - merchant_id, - active_attempt_ids, - connector_strings, - payment_methods, - ) - .await - .map_err(Into::into) - .into_report() - } - } - - #[inline] - async fn add_connector_txn_id_to_reverse_lookup( - store: &Store, - key: &str, - merchant_id: &str, - updated_attempt_attempt_id: &str, - connector_transaction_id: &str, - ) -> CustomResult { - let conn = connection::pg_connection_write(store).await?; - let field = format!("pa_{}", updated_attempt_attempt_id); - ReverseLookupNew { - lookup_id: format!("{}_{}", merchant_id, connector_transaction_id), - pk_id: key.to_owned(), - sk_id: field.clone(), - source: "payment_attempt".to_string(), - } - .insert(&conn) - .await - .map_err(Into::::into) - .into_report() - } - - #[inline] - async fn add_preprocessing_id_to_reverse_lookup( - store: &Store, - key: &str, - merchant_id: &str, - updated_attempt_attempt_id: &str, - preprocessing_id: &str, - ) -> CustomResult { - let conn = connection::pg_connection_write(store).await?; - let field = format!("pa_{}", updated_attempt_attempt_id); - ReverseLookupNew { - lookup_id: format!("{}_{}", merchant_id, preprocessing_id), - pk_id: key.to_owned(), - sk_id: field.clone(), - source: "payment_attempt".to_string(), - } - .insert(&conn) - .await - .map_err(Into::::into) - .into_report() - } -} diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index fa87aedaa5..a89de10f39 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -89,7 +89,12 @@ impl AppState { .await .expect("Failed to create store"), ), - StorageImpl::Mock => Box::new(MockDb::new().await), + #[allow(clippy::expect_used)] + StorageImpl::Mock => Box::new( + MockDb::new(&conf.redis) + .await + .expect("Failed to create mock store"), + ), }; #[cfg(feature = "kms")] diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index af989ac2f5..4c1c3d8c3a 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -18,6 +18,7 @@ pub use api_models::{ payouts as payout_types, }; use common_utils::{pii, pii::Email}; +use data_models::mandates::MandateData; use error_stack::{IntoReport, ResultExt}; use masking::Secret; @@ -350,7 +351,7 @@ pub struct PaymentsAuthorizeData { pub setup_future_usage: Option, pub mandate_id: Option, pub off_session: Option, - pub setup_mandate_details: Option, + pub setup_mandate_details: Option, pub browser_info: Option, pub order_details: Option>, pub order_category: Option, @@ -411,7 +412,7 @@ pub struct PaymentsPreProcessingData { pub email: Option, pub currency: Option, pub payment_method_type: Option, - pub setup_mandate_details: Option, + pub setup_mandate_details: Option, pub capture_method: Option, pub order_details: Option>, pub router_return_url: Option, @@ -432,7 +433,7 @@ pub struct CompleteAuthorizeData { pub setup_future_usage: Option, pub mandate_id: Option, pub off_session: Option, - pub setup_mandate_details: Option, + pub setup_mandate_details: Option, pub redirect_response: Option, pub browser_info: Option, pub connector_transaction_id: Option, @@ -502,7 +503,7 @@ pub struct VerifyRequestData { pub mandate_id: Option, pub setup_future_usage: Option, pub off_session: Option, - pub setup_mandate_details: Option, + pub setup_mandate_details: Option, pub router_return_url: Option, pub browser_info: Option, pub email: Option, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index b2e1617e5e..23c9f850da 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -41,7 +41,7 @@ pub trait ConnectorAccessToken: pub trait ConnectorTransactionId: ConnectorCommon + Sync { fn connector_transaction_id( &self, - payment_attempt: diesel_models::payment_attempt::PaymentAttempt, + payment_attempt: data_models::payments::payment_attempt::PaymentAttempt, ) -> Result, errors::ApiErrorResponse> { Ok(payment_attempt.connector_transaction_id) } diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index f266e67af1..eecaac6ced 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -1,19 +1,17 @@ pub use api_models::payments::{ AcceptanceType, Address, AddressDetails, Amount, AuthenticationForStartResponse, Card, - CryptoData, CustomerAcceptance, HeaderPayload, MandateData, MandateTransactionType, - MandateType, MandateValidationFields, NextActionType, OnlineMandate, PayLaterData, - PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, - PaymentListResponse, PaymentListResponseV2, PaymentMethodData, PaymentMethodDataResponse, - PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsApproveRequest, - PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsRedirectRequest, + CryptoData, CustomerAcceptance, HeaderPayload, MandateAmountData, MandateData, + MandateTransactionType, MandateType, MandateValidationFields, NextActionType, OnlineMandate, + PayLaterData, PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, + PaymentListFilters, PaymentListResponse, PaymentListResponseV2, PaymentMethodData, + PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, + PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest, PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, TimeRange, UrlDetails, VerifyRequest, VerifyResponse, WalletData, }; use error_stack::{IntoReport, ResultExt}; -use masking::PeekInterface; -use time::PrimitiveDateTime; use crate::{ core::errors, @@ -35,29 +33,6 @@ impl PaymentsRequestExt for PaymentsRequest { } } -pub(crate) trait CustomerAcceptanceExt { - fn get_ip_address(&self) -> Option; - fn get_user_agent(&self) -> Option; - fn get_accepted_at(&self) -> PrimitiveDateTime; -} - -impl CustomerAcceptanceExt for CustomerAcceptance { - fn get_ip_address(&self) -> Option { - self.online - .as_ref() - .and_then(|data| data.ip_address.as_ref().map(|ip| ip.peek().to_owned())) - } - - fn get_user_agent(&self) -> Option { - self.online.as_ref().map(|data| data.user_agent.clone()) - } - - fn get_accepted_at(&self) -> PrimitiveDateTime { - self.accepted_at - .unwrap_or_else(common_utils::date_time::now) - } -} - impl super::Router for PaymentsRequest {} // Core related api layer. diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs index 37e1c14ae4..9995f7cce8 100644 --- a/crates/router/src/types/storage.rs +++ b/crates/router/src/types/storage.rs @@ -29,14 +29,20 @@ pub mod payouts; mod query; pub mod refund; -pub use data_models::payments::payment_intent::{ - PaymentIntent, PaymentIntentNew, PaymentIntentUpdate, +pub use data_models::payments::{ + payment_attempt::{PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate}, + payment_intent::{PaymentIntent, PaymentIntentNew, PaymentIntentUpdate}, }; pub use self::{ address::*, api_keys::*, capture::*, cards_info::*, configs::*, connector_response::*, customers::*, dispute::*, ephemeral_key::*, events::*, file::*, locker_mock_up::*, mandate::*, - merchant_account::*, merchant_connector_account::*, merchant_key_store::*, payment_attempt::*, - payment_method::*, payout_attempt::*, payouts::*, process_tracker::*, refund::*, - reverse_lookup::*, + merchant_account::*, merchant_connector_account::*, merchant_key_store::*, payment_method::*, + payout_attempt::*, payouts::*, process_tracker::*, refund::*, reverse_lookup::*, }; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct RoutingData { + pub routed_through: Option, + pub algorithm: Option, +} diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index c7631282b2..1a9af97329 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -1,17 +1,11 @@ -pub use diesel_models::payment_attempt::{ - PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate, PaymentAttemptUpdateInternal, +pub use data_models::payments::payment_attempt::{ + PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate, }; use diesel_models::{capture::CaptureNew, enums}; use error_stack::ResultExt; use crate::{core::errors, errors::RouterResult, utils::OptionExt}; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RoutingData { - pub routed_through: Option, - pub algorithm: Option, -} - pub trait PaymentAttemptExt { fn make_new_capture( &self, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index c2a10abd67..9a0e0a385e 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -180,6 +180,58 @@ impl ForeignFrom for api_models::payments::Man } } +// TODO: remove foreign from since this conversion won't be needed in the router crate once data models is treated as a single & primary source of truth for structure information +impl ForeignFrom for data_models::mandates::MandateData { + fn foreign_from(d: api_models::payments::MandateData) -> Self { + Self { + customer_acceptance: d.customer_acceptance.map(|d| { + data_models::mandates::CustomerAcceptance { + acceptance_type: match d.acceptance_type { + api_models::payments::AcceptanceType::Online => { + data_models::mandates::AcceptanceType::Online + } + api_models::payments::AcceptanceType::Offline => { + data_models::mandates::AcceptanceType::Offline + } + }, + accepted_at: d.accepted_at, + online: d.online.map(|d| data_models::mandates::OnlineMandate { + ip_address: d.ip_address, + user_agent: d.user_agent, + }), + } + }), + mandate_type: d.mandate_type.map(|d| match d { + api_models::payments::MandateType::MultiUse(Some(i)) => { + data_models::mandates::MandateDataType::MultiUse(Some( + data_models::mandates::MandateAmountData { + amount: i.amount, + currency: i.currency, + start_date: i.start_date, + end_date: i.end_date, + metadata: i.metadata, + }, + )) + } + api_models::payments::MandateType::SingleUse(i) => { + data_models::mandates::MandateDataType::SingleUse( + data_models::mandates::MandateAmountData { + amount: i.amount, + currency: i.currency, + start_date: i.start_date, + end_date: i.end_date, + metadata: i.metadata, + }, + ) + } + api_models::payments::MandateType::MultiUse(None) => { + data_models::mandates::MandateDataType::MultiUse(None) + } + }), + } + } +} + impl ForeignFrom for storage_enums::MandateAmountData { fn foreign_from(from: api_models::payments::MandateAmountData) -> Self { Self { diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 2452676378..4ac092125a 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -15,6 +15,7 @@ pub use common_utils::{ fp_utils::when, validation::validate_email, }; +use data_models::payments::payment_intent::PaymentIntent; use error_stack::{IntoReport, ResultExt}; use image::Luma; use nanoid::nanoid; @@ -23,17 +24,14 @@ use serde::de::DeserializeOwned; use serde_json::Value; use uuid::Uuid; -pub use self::{ - ext_traits::{OptionExt, ValidateCall}, - storage::PaymentIntent, -}; +pub use self::ext_traits::{OptionExt, ValidateCall}; use crate::{ consts, core::errors::{self, CustomResult, RouterResult, StorageErrorExt}, db::StorageInterface, logger, routes::metrics, - types::{self, domain, storage}, + types::{self, domain}, }; pub mod error_parser { diff --git a/crates/router/src/utils/db_utils.rs b/crates/router/src/utils/db_utils.rs index c169de293d..febc226c02 100644 --- a/crates/router/src/utils/db_utils.rs +++ b/crates/router/src/utils/db_utils.rs @@ -1,6 +1,5 @@ use crate::{core::errors, routes::metrics}; -#[cfg(feature = "kv_store")] /// Generates hscan field pattern. Suppose the field is pa_1234_ref_1211 it will generate /// pa_1234_ref_* pub fn generate_hscan_pattern_for_refund(sk: &str) -> String { @@ -11,17 +10,6 @@ pub fn generate_hscan_pattern_for_refund(sk: &str) -> String { .join("_") } -#[cfg(feature = "kv_store")] -/// Generates hscan field pattern. Suppose the field is pa_1234 it will generate -/// pa_* -pub fn generate_hscan_pattern_for_attempt(sk: &str) -> String { - sk.split('_') - .take(1) - .chain(["*"]) - .collect::>() - .join("_") -} - // The first argument should be a future while the second argument should be a closure that returns a future for a database call pub async fn try_redis_get_else_try_database_get( redis_fut: RFut, diff --git a/crates/scheduler/src/db/process_tracker.rs b/crates/scheduler/src/db/process_tracker.rs index 9cabdf7797..728c146a64 100644 --- a/crates/scheduler/src/db/process_tracker.rs +++ b/crates/scheduler/src/db/process_tracker.rs @@ -3,7 +3,7 @@ pub use diesel_models as storage; use diesel_models::enums as storage_enums; use error_stack::{IntoReport, ResultExt}; use serde::Serialize; -use storage_impl::{connection, errors, MockDb}; +use storage_impl::{connection, errors, mock_db::MockDb}; use time::PrimitiveDateTime; use crate::{errors as sch_errors, metrics, scheduler::Store, SchedulerInterface}; diff --git a/crates/scheduler/src/db/queue.rs b/crates/scheduler/src/db/queue.rs index 870b2b8179..2c02b405d8 100644 --- a/crates/scheduler/src/db/queue.rs +++ b/crates/scheduler/src/db/queue.rs @@ -2,7 +2,7 @@ use common_utils::errors::CustomResult; use diesel_models::process_tracker as storage; use redis_interface::{errors::RedisError, RedisEntryId, SetnxReply}; use router_env::logger; -use storage_impl::{redis::kv_store::RedisConnInterface, MockDb}; +use storage_impl::{mock_db::MockDb, redis::kv_store::RedisConnInterface}; use crate::{errors::ProcessTrackerError, scheduler::Store}; diff --git a/crates/scheduler/src/scheduler.rs b/crates/scheduler/src/scheduler.rs index 15757c6e77..273f10819f 100644 --- a/crates/scheduler/src/scheduler.rs +++ b/crates/scheduler/src/scheduler.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use common_utils::errors::CustomResult; +use storage_impl::mock_db::MockDb; #[cfg(feature = "kv_store")] use storage_impl::KVRouterStore; -use storage_impl::MockDb; #[cfg(not(feature = "kv_store"))] use storage_impl::RouterStore; use tokio::sync::mpsc; diff --git a/crates/storage_impl/Cargo.toml b/crates/storage_impl/Cargo.toml index 778277523a..2226baa5c3 100644 --- a/crates/storage_impl/Cargo.toml +++ b/crates/storage_impl/Cargo.toml @@ -42,6 +42,7 @@ mime = "0.3.17" moka = { version = "0.11.3", features = ["future"] } once_cell = "1.18.0" ring = "0.16.20" -serde = { version = "1.0.163", features = ["derive"] } thiserror = "1.0.40" tokio = { version = "1.28.2", features = ["rt-multi-thread"] } +serde = { version = "1.0.185", features = ["derive"] } +serde_json = "1.0.105" diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index 5ff4d000b8..a7bc9e13cf 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -1,26 +1,24 @@ use std::sync::Arc; -use common_utils::errors::CustomResult; -use data_models::{ - errors::{StorageError, StorageResult}, - payments::payment_intent::PaymentIntent, -}; +use data_models::errors::{StorageError, StorageResult}; use diesel_models::{self as store}; use error_stack::ResultExt; -use futures::lock::Mutex; use masking::StrongSecret; use redis::{kv_store::RedisConnInterface, RedisStore}; pub mod config; pub mod connection; pub mod database; pub mod errors; +mod lookup; pub mod metrics; +pub mod mock_db; pub mod payments; pub mod redis; pub mod refund; mod utils; use database::store::PgPool; +pub use mock_db::MockDb; use redis_interface::errors::RedisError; pub use crate::database::store::DatabaseStore; @@ -168,6 +166,7 @@ impl RedisConnInterface for KVRouterStore { self.router_store.get_redis_conn() } } + impl KVRouterStore { pub fn from_store( store: RouterStore, @@ -214,60 +213,6 @@ impl KVRouterStore { } } -#[derive(Clone)] -pub struct MockDb { - pub addresses: Arc>>, - pub configs: Arc>>, - pub merchant_accounts: Arc>>, - pub merchant_connector_accounts: Arc>>, - pub payment_attempts: Arc>>, - pub payment_intents: Arc>>, - pub payment_methods: Arc>>, - pub customers: Arc>>, - pub refunds: Arc>>, - pub processes: Arc>>, - pub connector_response: Arc>>, - // pub redis: Arc, - pub api_keys: Arc>>, - pub ephemeral_keys: Arc>>, - pub cards_info: Arc>>, - pub events: Arc>>, - pub disputes: Arc>>, - pub lockers: Arc>>, - pub mandates: Arc>>, - pub captures: Arc>>, - pub merchant_key_store: Arc>>, - pub business_profiles: Arc>>, -} - -impl MockDb { - pub async fn new() -> Self { - Self { - addresses: Default::default(), - configs: Default::default(), - merchant_accounts: Default::default(), - merchant_connector_accounts: Default::default(), - payment_attempts: Default::default(), - payment_intents: Default::default(), - payment_methods: Default::default(), - customers: Default::default(), - refunds: Default::default(), - processes: Default::default(), - connector_response: Default::default(), - // redis: Arc::new(crate::connection::redis_connection(&redis).await), - api_keys: Default::default(), - ephemeral_keys: Default::default(), - cards_info: Default::default(), - events: Default::default(), - disputes: Default::default(), - lockers: Default::default(), - mandates: Default::default(), - captures: Default::default(), - merchant_key_store: Default::default(), - business_profiles: Default::default(), - } - } -} // TODO: This should not be used beyond this crate // Remove the pub modified once StorageScheme usage is completed pub trait DataModelExt { @@ -294,14 +239,6 @@ impl DataModelExt for data_models::MerchantStorageScheme { } } -impl RedisConnInterface for MockDb { - fn get_redis_conn( - &self, - ) -> Result, error_stack::Report> { - Err(RedisError::RedisConnectionError.into()) - } -} - pub(crate) fn diesel_error_to_data_error( diesel_error: &diesel_models::errors::DatabaseError, ) -> StorageError { diff --git a/crates/storage_impl/src/lookup.rs b/crates/storage_impl/src/lookup.rs new file mode 100644 index 0000000000..e6ea1ac8bf --- /dev/null +++ b/crates/storage_impl/src/lookup.rs @@ -0,0 +1,72 @@ +use common_utils::errors::CustomResult; +use data_models::errors; +use diesel_models::reverse_lookup::{ + ReverseLookup as DieselReverseLookup, ReverseLookupNew as DieselReverseLookupNew, +}; +use error_stack::{IntoReport, ResultExt}; + +use crate::{redis::cache::get_or_populate_redis, DatabaseStore, KVRouterStore, RouterStore}; + +#[async_trait::async_trait] +pub trait ReverseLookupInterface { + async fn insert_reverse_lookup( + &self, + _new: DieselReverseLookupNew, + ) -> CustomResult; + async fn get_lookup_by_lookup_id( + &self, + _id: &str, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl ReverseLookupInterface for RouterStore { + async fn insert_reverse_lookup( + &self, + new: DieselReverseLookupNew, + ) -> CustomResult { + let conn = self + .get_master_pool() + .get() + .await + .into_report() + .change_context(errors::StorageError::DatabaseConnectionError)?; + new.insert(&conn).await.map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + } + + async fn get_lookup_by_lookup_id( + &self, + id: &str, + ) -> CustomResult { + let database_call = || async { + let conn = crate::utils::pg_connection_read(self).await?; + DieselReverseLookup::find_by_lookup_id(id, &conn) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + }; + get_or_populate_redis(self, id, database_call).await + } +} + +#[async_trait::async_trait] +impl ReverseLookupInterface for KVRouterStore { + async fn insert_reverse_lookup( + &self, + new: DieselReverseLookupNew, + ) -> CustomResult { + self.router_store.insert_reverse_lookup(new).await + } + + async fn get_lookup_by_lookup_id( + &self, + id: &str, + ) -> CustomResult { + self.router_store.get_lookup_by_lookup_id(id).await + } +} diff --git a/crates/storage_impl/src/mock_db.rs b/crates/storage_impl/src/mock_db.rs new file mode 100644 index 0000000000..81242af9c7 --- /dev/null +++ b/crates/storage_impl/src/mock_db.rs @@ -0,0 +1,75 @@ +use std::sync::Arc; + +use data_models::{ + errors::StorageError, + payments::{payment_attempt::PaymentAttempt, payment_intent::PaymentIntent}, +}; +use diesel_models::{self as store}; +use error_stack::ResultExt; +use futures::lock::Mutex; +use redis_interface::RedisSettings; + +use crate::redis::RedisStore; + +pub mod payment_attempt; +pub mod payment_intent; +pub mod redis_conn; + +#[derive(Clone)] +pub struct MockDb { + pub addresses: Arc>>, + pub configs: Arc>>, + pub merchant_accounts: Arc>>, + pub merchant_connector_accounts: Arc>>, + pub payment_attempts: Arc>>, + pub payment_intents: Arc>>, + pub payment_methods: Arc>>, + pub customers: Arc>>, + pub refunds: Arc>>, + pub processes: Arc>>, + pub connector_response: Arc>>, + pub redis: Arc, + pub api_keys: Arc>>, + pub ephemeral_keys: Arc>>, + pub cards_info: Arc>>, + pub events: Arc>>, + pub disputes: Arc>>, + pub lockers: Arc>>, + pub mandates: Arc>>, + pub captures: Arc>>, + pub merchant_key_store: Arc>>, + pub business_profiles: Arc>>, +} + +impl MockDb { + pub async fn new(redis: &RedisSettings) -> error_stack::Result { + Ok(Self { + addresses: Default::default(), + configs: Default::default(), + merchant_accounts: Default::default(), + merchant_connector_accounts: Default::default(), + payment_attempts: Default::default(), + payment_intents: Default::default(), + payment_methods: Default::default(), + customers: Default::default(), + refunds: Default::default(), + processes: Default::default(), + connector_response: Default::default(), + redis: Arc::new( + RedisStore::new(redis) + .await + .change_context(StorageError::InitializationError)?, + ), + api_keys: Default::default(), + ephemeral_keys: Default::default(), + cards_info: Default::default(), + events: Default::default(), + disputes: Default::default(), + lockers: Default::default(), + mandates: Default::default(), + captures: Default::default(), + merchant_key_store: Default::default(), + business_profiles: Default::default(), + }) + } +} diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs new file mode 100644 index 0000000000..32b33d5967 --- /dev/null +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -0,0 +1,199 @@ +use api_models::enums::{Connector, PaymentMethod}; +use common_utils::errors::CustomResult; +use data_models::{ + errors::StorageError, + payments::payment_attempt::{ + PaymentAttempt, PaymentAttemptInterface, PaymentAttemptNew, PaymentAttemptUpdate, + }, + MerchantStorageScheme, +}; + +use super::MockDb; +use crate::DataModelExt; + +#[async_trait::async_trait] +impl PaymentAttemptInterface for MockDb { + async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &self, + _payment_id: &str, + _merchant_id: &str, + _attempt_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + + async fn get_filters_for_payments( + &self, + _pi: &[data_models::payments::payment_intent::PaymentIntent], + _merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult + { + Err(StorageError::MockDbError)? + } + + async fn get_total_count_of_filtered_payment_attempts( + &self, + _merchant_id: &str, + _active_attempt_ids: &[String], + _connector: Option>, + _payment_methods: Option>, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + Err(StorageError::MockDbError)? + } + + async fn find_payment_attempt_by_attempt_id_merchant_id( + &self, + _attempt_id: &str, + _merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + + async fn find_payment_attempt_by_preprocessing_id_merchant_id( + &self, + _preprocessing_id: &str, + _merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + + async fn find_payment_attempt_by_merchant_id_connector_txn_id( + &self, + _merchant_id: &str, + _connector_txn_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + + async fn find_attempts_by_merchant_id_payment_id( + &self, + _merchant_id: &str, + _payment_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult, StorageError> { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + + #[allow(clippy::panic)] + async fn insert_payment_attempt( + &self, + payment_attempt: PaymentAttemptNew, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let mut payment_attempts = self.payment_attempts.lock().await; + #[allow(clippy::as_conversions)] + let id = payment_attempts.len() as i32; + let time = common_utils::date_time::now(); + + let payment_attempt = PaymentAttempt { + id, + payment_id: payment_attempt.payment_id, + merchant_id: payment_attempt.merchant_id, + attempt_id: payment_attempt.attempt_id, + status: payment_attempt.status, + amount: payment_attempt.amount, + currency: payment_attempt.currency, + save_to_locker: payment_attempt.save_to_locker, + connector: payment_attempt.connector, + error_message: payment_attempt.error_message, + offer_amount: payment_attempt.offer_amount, + surcharge_amount: payment_attempt.surcharge_amount, + tax_amount: payment_attempt.tax_amount, + payment_method_id: payment_attempt.payment_method_id, + payment_method: payment_attempt.payment_method, + connector_transaction_id: None, + capture_method: payment_attempt.capture_method, + capture_on: payment_attempt.capture_on, + confirm: payment_attempt.confirm, + authentication_type: payment_attempt.authentication_type, + created_at: payment_attempt.created_at.unwrap_or(time), + modified_at: payment_attempt.modified_at.unwrap_or(time), + last_synced: payment_attempt.last_synced, + cancellation_reason: payment_attempt.cancellation_reason, + amount_to_capture: payment_attempt.amount_to_capture, + mandate_id: None, + browser_info: None, + payment_token: None, + error_code: payment_attempt.error_code, + connector_metadata: None, + payment_experience: payment_attempt.payment_experience, + payment_method_type: payment_attempt.payment_method_type, + payment_method_data: payment_attempt.payment_method_data, + business_sub_label: payment_attempt.business_sub_label, + straight_through_algorithm: payment_attempt.straight_through_algorithm, + mandate_details: payment_attempt.mandate_details, + preprocessing_step_id: payment_attempt.preprocessing_step_id, + error_reason: payment_attempt.error_reason, + multiple_capture_count: payment_attempt.multiple_capture_count, + connector_response_reference_id: None, + }; + payment_attempts.push(payment_attempt.clone()); + Ok(payment_attempt) + } + + // safety: only used for testing + #[allow(clippy::unwrap_used)] + async fn update_payment_attempt_with_attempt_id( + &self, + this: PaymentAttempt, + payment_attempt: PaymentAttemptUpdate, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let mut payment_attempts = self.payment_attempts.lock().await; + + let item = payment_attempts + .iter_mut() + .find(|item| item.attempt_id == this.attempt_id) + .unwrap(); + + *item = PaymentAttempt::from_storage_model( + payment_attempt + .to_storage_model() + .apply_changeset(this.to_storage_model()), + ); + + Ok(item.clone()) + } + + async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( + &self, + _connector_transaction_id: &str, + _payment_id: &str, + _merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + + // safety: only used for testing + #[allow(clippy::unwrap_used)] + async fn find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( + &self, + payment_id: &str, + merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let payment_attempts = self.payment_attempts.lock().await; + + Ok(payment_attempts + .iter() + .find(|payment_attempt| { + payment_attempt.payment_id == payment_id + && payment_attempt.merchant_id == merchant_id + }) + .cloned() + .unwrap()) + } +} diff --git a/crates/router/src/db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs similarity index 68% rename from crates/router/src/db/payment_intent.rs rename to crates/storage_impl/src/mock_db/payment_intent.rs index 8a70a208de..43d7207d45 100644 --- a/crates/router/src/db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -1,19 +1,14 @@ -use data_models::payments::payment_intent::{ - PaymentIntent, PaymentIntentInterface, PaymentIntentNew, -}; -#[cfg(feature = "olap")] -use data_models::payments::{ - payment_attempt::PaymentAttempt, payment_intent::PaymentIntentFetchConstraints, +use common_utils::errors::CustomResult; +use data_models::{ + errors::StorageError, + payments::payment_intent::{ + PaymentIntent, PaymentIntentInterface, PaymentIntentNew, PaymentIntentUpdate, + }, + MerchantStorageScheme, }; use error_stack::{IntoReport, ResultExt}; use super::MockDb; -#[cfg(feature = "olap")] -use crate::types::api; -use crate::{ - core::errors::{self, CustomResult}, - types::storage::{self as types, enums}, -}; #[async_trait::async_trait] impl PaymentIntentInterface for MockDb { @@ -21,58 +16,64 @@ impl PaymentIntentInterface for MockDb { async fn filter_payment_intent_by_constraints( &self, _merchant_id: &str, - _filters: &PaymentIntentFetchConstraints, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult, errors::DataStorageError> { + _filters: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult, StorageError> { // [#172]: Implement function for `MockDb` - Err(errors::DataStorageError::MockDbError)? + Err(StorageError::MockDbError)? } #[cfg(feature = "olap")] async fn filter_payment_intents_by_time_range_constraints( &self, _merchant_id: &str, - _time_range: &api::TimeRange, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult, errors::DataStorageError> { + _time_range: &api_models::payments::TimeRange, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult, StorageError> { // [#172]: Implement function for `MockDb` - Err(errors::DataStorageError::MockDbError)? + Err(StorageError::MockDbError)? + } + #[cfg(feature = "olap")] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + _merchant_id: &str, + _constraints: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + _storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result, StorageError> { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? } #[cfg(feature = "olap")] async fn get_filtered_payment_intents_attempt( &self, _merchant_id: &str, - _constraints: &PaymentIntentFetchConstraints, - _storage_scheme: enums::MerchantStorageScheme, - ) -> error_stack::Result, errors::DataStorageError> { + _constraints: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + _storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result< + Vec<( + PaymentIntent, + data_models::payments::payment_attempt::PaymentAttempt, + )>, + StorageError, + > { // [#172]: Implement function for `MockDb` - Err(errors::DataStorageError::MockDbError)? - } - - #[cfg(feature = "olap")] - async fn get_filtered_active_attempt_ids_for_total_count( - &self, - _merchant_id: &str, - _constraints: &PaymentIntentFetchConstraints, - _storage_scheme: enums::MerchantStorageScheme, - ) -> error_stack::Result, errors::DataStorageError> { - // [#172]: Implement function for `MockDb` - Err(errors::DataStorageError::MockDbError)? + Err(StorageError::MockDbError)? } #[allow(clippy::panic)] async fn insert_payment_intent( &self, new: PaymentIntentNew, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { let mut payment_intents = self.payment_intents.lock().await; let time = common_utils::date_time::now(); let payment_intent = PaymentIntent { + #[allow(clippy::as_conversions)] id: payment_intents .len() .try_into() .into_report() - .change_context(errors::DataStorageError::MockDbError)?, + .change_context(StorageError::MockDbError)?, payment_id: new.payment_id, merchant_id: new.merchant_id, status: new.status, @@ -104,6 +105,7 @@ impl PaymentIntentInterface for MockDb { attempt_count: new.attempt_count, profile_id: new.profile_id, merchant_decision: new.merchant_decision, + payment_confirm_source: new.payment_confirm_source, }; payment_intents.push(payment_intent.clone()); Ok(payment_intent) @@ -114,9 +116,9 @@ impl PaymentIntentInterface for MockDb { async fn update_payment_intent( &self, this: PaymentIntent, - update: types::PaymentIntentUpdate, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { + update: PaymentIntentUpdate, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { let mut payment_intents = self.payment_intents.lock().await; let payment_intent = payment_intents .iter_mut() @@ -132,8 +134,8 @@ impl PaymentIntentInterface for MockDb { &self, payment_id: &str, merchant_id: &str, - _storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult { + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { let payment_intents = self.payment_intents.lock().await; Ok(payment_intents diff --git a/crates/storage_impl/src/mock_db/redis_conn.rs b/crates/storage_impl/src/mock_db/redis_conn.rs new file mode 100644 index 0000000000..142c750570 --- /dev/null +++ b/crates/storage_impl/src/mock_db/redis_conn.rs @@ -0,0 +1,14 @@ +use std::sync::Arc; + +use redis_interface::errors::RedisError; + +use super::MockDb; +use crate::redis::kv_store::RedisConnInterface; + +impl RedisConnInterface for MockDb { + fn get_redis_conn( + &self, + ) -> Result, error_stack::Report> { + self.redis.get_redis_conn() + } +} diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 218d87cdfa..c2a5a97da1 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1,13 +1,814 @@ +use api_models::enums::{Connector, PaymentMethod}; +use common_utils::errors::CustomResult; use data_models::{ + errors, mandates::{MandateAmountData, MandateDataType}, - payments::payment_attempt::PaymentAttempt, + payments::{ + payment_attempt::{ + PaymentAttempt, PaymentAttemptInterface, PaymentAttemptNew, PaymentAttemptUpdate, + PaymentListFilters, + }, + payment_intent::PaymentIntent, + }, + MerchantStorageScheme, }; use diesel_models::{ enums::{MandateAmountData as DieselMandateAmountData, MandateDataType as DieselMandateType}, - payment_attempt::PaymentAttempt as DieselPaymentAttempt, + kv, + payment_attempt::{ + PaymentAttempt as DieselPaymentAttempt, PaymentAttemptNew as DieselPaymentAttemptNew, + PaymentAttemptUpdate as DieselPaymentAttemptUpdate, + }, + reverse_lookup::{ReverseLookup, ReverseLookupNew}, +}; +use error_stack::{IntoReport, ResultExt}; +use redis_interface::HsetnxReply; + +use crate::{ + lookup::ReverseLookupInterface, + redis::kv_store::{PartitionKey, RedisConnInterface}, + utils::{ + generate_hscan_pattern_for_attempt, pg_connection_read, pg_connection_write, + try_redis_get_else_try_database_get, + }, + DataModelExt, DatabaseStore, KVRouterStore, RouterStore, }; -use crate::DataModelExt; +#[async_trait::async_trait] +impl PaymentAttemptInterface for RouterStore { + async fn insert_payment_attempt( + &self, + payment_attempt: PaymentAttemptNew, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_write(self).await?; + payment_attempt + .to_storage_model() + .insert(&conn) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(PaymentAttempt::from_storage_model) + } + + async fn update_payment_attempt_with_attempt_id( + &self, + this: PaymentAttempt, + payment_attempt: PaymentAttemptUpdate, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_write(self).await?; + this.to_storage_model() + .update_with_attempt_id(&conn, payment_attempt.to_storage_model()) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(PaymentAttempt::from_storage_model) + } + + async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( + &self, + connector_transaction_id: &str, + payment_id: &str, + merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_read(self).await?; + DieselPaymentAttempt::find_by_connector_transaction_id_payment_id_merchant_id( + &conn, + connector_transaction_id, + payment_id, + merchant_id, + ) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(PaymentAttempt::from_storage_model) + } + + async fn find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( + &self, + payment_id: &str, + merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_read(self).await?; + DieselPaymentAttempt::find_last_successful_attempt_by_payment_id_merchant_id( + &conn, + payment_id, + merchant_id, + ) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(PaymentAttempt::from_storage_model) + } + + async fn find_payment_attempt_by_merchant_id_connector_txn_id( + &self, + merchant_id: &str, + connector_txn_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_read(self).await?; + DieselPaymentAttempt::find_by_merchant_id_connector_txn_id( + &conn, + merchant_id, + connector_txn_id, + ) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(PaymentAttempt::from_storage_model) + } + + async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &self, + payment_id: &str, + merchant_id: &str, + attempt_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_read(self).await?; + + DieselPaymentAttempt::find_by_payment_id_merchant_id_attempt_id( + &conn, + payment_id, + merchant_id, + attempt_id, + ) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(PaymentAttempt::from_storage_model) + } + + async fn get_filters_for_payments( + &self, + pi: &[PaymentIntent], + merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_read(self).await?; + let intents = pi + .iter() + .cloned() + .map(|pi| pi.to_storage_model()) + .collect::>(); + DieselPaymentAttempt::get_filters_for_payments(&conn, intents.as_slice(), merchant_id) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map( + |(connector, currency, status, payment_method)| PaymentListFilters { + connector, + currency, + status, + payment_method, + }, + ) + } + + async fn find_payment_attempt_by_preprocessing_id_merchant_id( + &self, + preprocessing_id: &str, + merchant_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_read(self).await?; + + DieselPaymentAttempt::find_by_merchant_id_preprocessing_id( + &conn, + merchant_id, + preprocessing_id, + ) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(PaymentAttempt::from_storage_model) + } + + async fn find_attempts_by_merchant_id_payment_id( + &self, + merchant_id: &str, + payment_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult, errors::StorageError> { + let conn = pg_connection_read(self).await?; + DieselPaymentAttempt::find_by_merchant_id_payment_id(&conn, merchant_id, payment_id) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(|a| { + a.into_iter() + .map(PaymentAttempt::from_storage_model) + .collect() + }) + } + + async fn find_payment_attempt_by_attempt_id_merchant_id( + &self, + merchant_id: &str, + attempt_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_read(self).await?; + + DieselPaymentAttempt::find_by_merchant_id_attempt_id(&conn, merchant_id, attempt_id) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + .map(PaymentAttempt::from_storage_model) + } + + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &str, + active_attempt_ids: &[String], + connector: Option>, + payment_methods: Option>, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = self + .db_store + .get_replica_pool() + .get() + .await + .into_report() + .change_context(errors::StorageError::DatabaseConnectionError)?; + let connector_strings = connector.as_ref().map(|connector| { + connector + .iter() + .map(|c| c.to_string()) + .collect::>() + }); + DieselPaymentAttempt::get_total_count_of_attempts( + &conn, + merchant_id, + active_attempt_ids, + connector_strings, + payment_methods, + ) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) + } +} + +#[async_trait::async_trait] +impl PaymentAttemptInterface for KVRouterStore { + async fn insert_payment_attempt( + &self, + payment_attempt: PaymentAttemptNew, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .insert_payment_attempt(payment_attempt, storage_scheme) + .await + } + MerchantStorageScheme::RedisKv => { + let key = format!( + "{}_{}", + payment_attempt.merchant_id, payment_attempt.payment_id + ); + + let created_attempt = PaymentAttempt { + id: Default::default(), + payment_id: payment_attempt.payment_id.clone(), + merchant_id: payment_attempt.merchant_id.clone(), + attempt_id: payment_attempt.attempt_id.clone(), + status: payment_attempt.status, + amount: payment_attempt.amount, + currency: payment_attempt.currency, + save_to_locker: payment_attempt.save_to_locker, + connector: payment_attempt.connector.clone(), + error_message: payment_attempt.error_message.clone(), + offer_amount: payment_attempt.offer_amount, + surcharge_amount: payment_attempt.surcharge_amount, + tax_amount: payment_attempt.tax_amount, + payment_method_id: payment_attempt.payment_method_id.clone(), + payment_method: payment_attempt.payment_method, + connector_transaction_id: None, + capture_method: payment_attempt.capture_method, + capture_on: payment_attempt.capture_on, + confirm: payment_attempt.confirm, + authentication_type: payment_attempt.authentication_type, + created_at: payment_attempt + .created_at + .unwrap_or_else(common_utils::date_time::now), + modified_at: payment_attempt + .created_at + .unwrap_or_else(common_utils::date_time::now), + last_synced: payment_attempt.last_synced, + amount_to_capture: payment_attempt.amount_to_capture, + cancellation_reason: payment_attempt.cancellation_reason.clone(), + mandate_id: payment_attempt.mandate_id.clone(), + browser_info: payment_attempt.browser_info.clone(), + payment_token: payment_attempt.payment_token.clone(), + error_code: payment_attempt.error_code.clone(), + connector_metadata: payment_attempt.connector_metadata.clone(), + payment_experience: payment_attempt.payment_experience, + payment_method_type: payment_attempt.payment_method_type, + payment_method_data: payment_attempt.payment_method_data.clone(), + business_sub_label: payment_attempt.business_sub_label.clone(), + straight_through_algorithm: payment_attempt.straight_through_algorithm.clone(), + mandate_details: payment_attempt.mandate_details.clone(), + preprocessing_step_id: payment_attempt.preprocessing_step_id.clone(), + error_reason: payment_attempt.error_reason.clone(), + multiple_capture_count: payment_attempt.multiple_capture_count, + connector_response_reference_id: None, + }; + + let field = format!("pa_{}", created_attempt.attempt_id); + match self + .get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(errors::StorageError::RedisError(error)) + })? + .serialize_and_set_hash_field_if_not_exist(&key, &field, &created_attempt) + .await + { + Ok(HsetnxReply::KeyNotSet) => Err(errors::StorageError::DuplicateValue { + entity: "payment attempt", + key: Some(key), + }) + .into_report(), + Ok(HsetnxReply::KeySet) => { + let conn = pg_connection_write(self).await?; + + //Reverse lookup for attempt_id + ReverseLookupNew { + lookup_id: format!( + "{}_{}", + &created_attempt.merchant_id, &created_attempt.attempt_id, + ), + pk_id: key, + sk_id: field, + source: "payment_attempt".to_string(), + } + .insert(&conn) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + })?; + + let redis_entry = kv::TypedSql { + op: kv::DBOperation::Insert { + insertable: kv::Insertable::PaymentAttempt( + payment_attempt.to_storage_model(), + ), + }, + }; + self.push_to_drainer_stream::( + redis_entry, + PartitionKey::MerchantIdPaymentId { + merchant_id: &created_attempt.merchant_id, + payment_id: &created_attempt.payment_id, + }, + ) + .await + .change_context(errors::StorageError::KVError)?; + Ok(created_attempt) + } + Err(error) => Err(error.change_context(errors::StorageError::KVError)), + } + } + } + } + + async fn update_payment_attempt_with_attempt_id( + &self, + this: PaymentAttempt, + payment_attempt: PaymentAttemptUpdate, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .update_payment_attempt_with_attempt_id(this, payment_attempt, storage_scheme) + .await + } + MerchantStorageScheme::RedisKv => { + let key = format!("{}_{}", this.merchant_id, this.payment_id); + let old_connector_transaction_id = &this.connector_transaction_id; + let old_preprocessing_id = &this.preprocessing_step_id; + let updated_attempt = PaymentAttempt::from_storage_model( + payment_attempt + .clone() + .to_storage_model() + .apply_changeset(this.clone().to_storage_model()), + ); + // Check for database presence as well Maybe use a read replica here ? + let redis_value = serde_json::to_string(&updated_attempt) + .into_report() + .change_context(errors::StorageError::KVError)?; + let field = format!("pa_{}", updated_attempt.attempt_id); + let updated_attempt = self + .get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(errors::StorageError::RedisError(error)) + })? + .set_hash_fields(&key, (&field, &redis_value)) + .await + .map(|_| updated_attempt) + .change_context(errors::StorageError::KVError)?; + + match ( + old_connector_transaction_id, + &updated_attempt.connector_transaction_id, + ) { + (None, Some(connector_transaction_id)) => { + add_connector_txn_id_to_reverse_lookup( + &self.router_store, + key.as_str(), + this.merchant_id.as_str(), + updated_attempt.attempt_id.as_str(), + connector_transaction_id.as_str(), + ) + .await?; + } + (Some(old_connector_transaction_id), Some(connector_transaction_id)) => { + if old_connector_transaction_id.ne(connector_transaction_id) { + add_connector_txn_id_to_reverse_lookup( + &self.router_store, + key.as_str(), + this.merchant_id.as_str(), + updated_attempt.attempt_id.as_str(), + connector_transaction_id.as_str(), + ) + .await?; + } + } + (_, _) => {} + } + + match (old_preprocessing_id, &updated_attempt.preprocessing_step_id) { + (None, Some(preprocessing_id)) => { + add_preprocessing_id_to_reverse_lookup( + &self.router_store, + key.as_str(), + this.merchant_id.as_str(), + updated_attempt.attempt_id.as_str(), + preprocessing_id.as_str(), + ) + .await?; + } + (Some(old_preprocessing_id), Some(preprocessing_id)) => { + if old_preprocessing_id.ne(preprocessing_id) { + add_preprocessing_id_to_reverse_lookup( + &self.router_store, + key.as_str(), + this.merchant_id.as_str(), + updated_attempt.attempt_id.as_str(), + preprocessing_id.as_str(), + ) + .await?; + } + } + (_, _) => {} + } + + let redis_entry = kv::TypedSql { + op: kv::DBOperation::Update { + updatable: kv::Updateable::PaymentAttemptUpdate( + kv::PaymentAttemptUpdateMems { + orig: this.to_storage_model(), + update_data: payment_attempt.to_storage_model(), + }, + ), + }, + }; + self.push_to_drainer_stream::( + redis_entry, + PartitionKey::MerchantIdPaymentId { + merchant_id: &updated_attempt.merchant_id, + payment_id: &updated_attempt.payment_id, + }, + ) + .await + .change_context(errors::StorageError::KVError)?; + Ok(updated_attempt) + } + } + } + + async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( + &self, + connector_transaction_id: &str, + payment_id: &str, + merchant_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( + connector_transaction_id, + payment_id, + merchant_id, + storage_scheme, + ) + .await + } + MerchantStorageScheme::RedisKv => { + // We assume that PaymentAttempt <=> PaymentIntent is a one-to-one relation for now + let lookup_id = format!("{merchant_id}_{connector_transaction_id}"); + let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; + let key = &lookup.pk_id; + + try_redis_get_else_try_database_get( + self.get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(errors::StorageError::RedisError(error)) + })? + .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), + || async {self.router_store.find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id(connector_transaction_id, payment_id, merchant_id, storage_scheme).await}, + ) + .await + } + } + } + + async fn find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( + &self, + payment_id: &str, + merchant_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + self.router_store + .find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( + payment_id, + merchant_id, + storage_scheme, + ) + .await + } + + async fn find_payment_attempt_by_merchant_id_connector_txn_id( + &self, + merchant_id: &str, + connector_txn_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .find_payment_attempt_by_merchant_id_connector_txn_id( + merchant_id, + connector_txn_id, + storage_scheme, + ) + .await + } + MerchantStorageScheme::RedisKv => { + let lookup_id = format!("{merchant_id}_{connector_txn_id}"); + let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; + + let key = &lookup.pk_id; + try_redis_get_else_try_database_get( + self.get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(errors::StorageError::RedisError(error)) + })? + .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), + || async { + self.router_store + .find_payment_attempt_by_merchant_id_connector_txn_id( + merchant_id, + connector_txn_id, + storage_scheme, + ) + .await + }, + ) + .await + } + } + } + + async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &self, + payment_id: &str, + merchant_id: &str, + attempt_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + payment_id, + merchant_id, + attempt_id, + storage_scheme, + ) + .await + } + MerchantStorageScheme::RedisKv => { + let lookup_id = format!("{merchant_id}_{attempt_id}"); + let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; + let key = &lookup.pk_id; + try_redis_get_else_try_database_get( + self.get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(errors::StorageError::RedisError(error)) + })? + .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), + || async { + self.router_store + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + payment_id, + merchant_id, + attempt_id, + storage_scheme, + ) + .await + }, + ) + .await + } + } + } + + async fn find_payment_attempt_by_attempt_id_merchant_id( + &self, + attempt_id: &str, + merchant_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .find_payment_attempt_by_attempt_id_merchant_id( + attempt_id, + merchant_id, + storage_scheme, + ) + .await + } + MerchantStorageScheme::RedisKv => { + let lookup_id = format!("{merchant_id}_{attempt_id}"); + let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; + let key = &lookup.pk_id; + try_redis_get_else_try_database_get( + self.get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(errors::StorageError::RedisError(error)) + })? + .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), + || async { + self.router_store + .find_payment_attempt_by_attempt_id_merchant_id( + attempt_id, + merchant_id, + storage_scheme, + ) + .await + }, + ) + .await + } + } + } + + async fn find_payment_attempt_by_preprocessing_id_merchant_id( + &self, + preprocessing_id: &str, + merchant_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .find_payment_attempt_by_preprocessing_id_merchant_id( + preprocessing_id, + merchant_id, + storage_scheme, + ) + .await + } + MerchantStorageScheme::RedisKv => { + let lookup_id = format!("{merchant_id}_{preprocessing_id}"); + let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; + let key = &lookup.pk_id; + + try_redis_get_else_try_database_get( + self.get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(errors::StorageError::RedisError(error)) + })? + .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), + || async { + self.router_store + .find_payment_attempt_by_preprocessing_id_merchant_id( + preprocessing_id, + merchant_id, + storage_scheme, + ) + .await + }, + ) + .await + } + } + } + + async fn find_attempts_by_merchant_id_payment_id( + &self, + merchant_id: &str, + payment_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result, errors::StorageError> { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .find_attempts_by_merchant_id_payment_id( + merchant_id, + payment_id, + storage_scheme, + ) + .await + } + MerchantStorageScheme::RedisKv => { + let key = format!("{merchant_id}_{payment_id}"); + let lookup = self.get_lookup_by_lookup_id(&key).await?; + + let pattern = generate_hscan_pattern_for_attempt(&lookup.sk_id); + + self.get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(errors::StorageError::RedisError(error)) + })? + .hscan_and_deserialize(&key, &pattern, None) + .await + .change_context(errors::StorageError::KVError) + } + } + } + + async fn get_filters_for_payments( + &self, + pi: &[PaymentIntent], + merchant_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + self.router_store + .get_filters_for_payments(pi, merchant_id, storage_scheme) + .await + } + + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &str, + active_attempt_ids: &[String], + connector: Option>, + payment_methods: Option>, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + self.router_store + .get_total_count_of_filtered_payment_attempts( + merchant_id, + active_attempt_ids, + connector, + payment_methods, + storage_scheme, + ) + .await + } +} impl DataModelExt for MandateAmountData { type StorageModel = DieselMandateAmountData; @@ -154,3 +955,497 @@ impl DataModelExt for PaymentAttempt { } } } + +impl DataModelExt for PaymentAttemptNew { + type StorageModel = DieselPaymentAttemptNew; + + fn to_storage_model(self) -> Self::StorageModel { + DieselPaymentAttemptNew { + payment_id: self.payment_id, + merchant_id: self.merchant_id, + attempt_id: self.attempt_id, + status: self.status, + amount: self.amount, + currency: self.currency, + save_to_locker: self.save_to_locker, + connector: self.connector, + error_message: self.error_message, + offer_amount: self.offer_amount, + surcharge_amount: self.surcharge_amount, + tax_amount: self.tax_amount, + payment_method_id: self.payment_method_id, + payment_method: self.payment_method, + capture_method: self.capture_method, + capture_on: self.capture_on, + confirm: self.confirm, + authentication_type: self.authentication_type, + created_at: self.created_at, + modified_at: self.modified_at, + last_synced: self.last_synced, + cancellation_reason: self.cancellation_reason, + amount_to_capture: self.amount_to_capture, + mandate_id: self.mandate_id, + browser_info: self.browser_info, + payment_token: self.payment_token, + error_code: self.error_code, + connector_metadata: self.connector_metadata, + payment_experience: self.payment_experience, + payment_method_type: self.payment_method_type, + payment_method_data: self.payment_method_data, + business_sub_label: self.business_sub_label, + straight_through_algorithm: self.straight_through_algorithm, + preprocessing_step_id: self.preprocessing_step_id, + mandate_details: self.mandate_details.map(|d| d.to_storage_model()), + error_reason: self.error_reason, + connector_response_reference_id: self.connector_response_reference_id, + multiple_capture_count: self.multiple_capture_count, + } + } + + fn from_storage_model(storage_model: Self::StorageModel) -> Self { + Self { + payment_id: storage_model.payment_id, + merchant_id: storage_model.merchant_id, + attempt_id: storage_model.attempt_id, + status: storage_model.status, + amount: storage_model.amount, + currency: storage_model.currency, + save_to_locker: storage_model.save_to_locker, + connector: storage_model.connector, + error_message: storage_model.error_message, + offer_amount: storage_model.offer_amount, + surcharge_amount: storage_model.surcharge_amount, + tax_amount: storage_model.tax_amount, + payment_method_id: storage_model.payment_method_id, + payment_method: storage_model.payment_method, + capture_method: storage_model.capture_method, + capture_on: storage_model.capture_on, + confirm: storage_model.confirm, + authentication_type: storage_model.authentication_type, + created_at: storage_model.created_at, + modified_at: storage_model.modified_at, + last_synced: storage_model.last_synced, + cancellation_reason: storage_model.cancellation_reason, + amount_to_capture: storage_model.amount_to_capture, + mandate_id: storage_model.mandate_id, + browser_info: storage_model.browser_info, + payment_token: storage_model.payment_token, + error_code: storage_model.error_code, + connector_metadata: storage_model.connector_metadata, + payment_experience: storage_model.payment_experience, + payment_method_type: storage_model.payment_method_type, + payment_method_data: storage_model.payment_method_data, + business_sub_label: storage_model.business_sub_label, + straight_through_algorithm: storage_model.straight_through_algorithm, + preprocessing_step_id: storage_model.preprocessing_step_id, + mandate_details: storage_model + .mandate_details + .map(MandateDataType::from_storage_model), + error_reason: storage_model.error_reason, + connector_response_reference_id: storage_model.connector_response_reference_id, + multiple_capture_count: storage_model.multiple_capture_count, + } + } +} + +impl DataModelExt for PaymentAttemptUpdate { + type StorageModel = DieselPaymentAttemptUpdate; + + fn to_storage_model(self) -> Self::StorageModel { + match self { + Self::Update { + amount, + currency, + status, + authentication_type, + payment_method, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + amount_to_capture, + capture_method, + } => DieselPaymentAttemptUpdate::Update { + amount, + currency, + status, + authentication_type, + payment_method, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + amount_to_capture, + capture_method, + }, + Self::UpdateTrackers { + payment_token, + connector, + straight_through_algorithm, + } => DieselPaymentAttemptUpdate::UpdateTrackers { + payment_token, + connector, + straight_through_algorithm, + }, + Self::AuthenticationTypeUpdate { + authentication_type, + } => DieselPaymentAttemptUpdate::AuthenticationTypeUpdate { + authentication_type, + }, + Self::ConfirmUpdate { + amount, + currency, + status, + authentication_type, + payment_method, + browser_info, + connector, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + straight_through_algorithm, + error_code, + error_message, + } => DieselPaymentAttemptUpdate::ConfirmUpdate { + amount, + currency, + status, + authentication_type, + payment_method, + browser_info, + connector, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + straight_through_algorithm, + error_code, + error_message, + }, + Self::VoidUpdate { + status, + cancellation_reason, + } => DieselPaymentAttemptUpdate::VoidUpdate { + status, + cancellation_reason, + }, + Self::ResponseUpdate { + status, + connector, + connector_transaction_id, + authentication_type, + payment_method_id, + mandate_id, + connector_metadata, + payment_token, + error_code, + error_message, + error_reason, + connector_response_reference_id, + } => DieselPaymentAttemptUpdate::ResponseUpdate { + status, + connector, + connector_transaction_id, + authentication_type, + payment_method_id, + mandate_id, + connector_metadata, + payment_token, + error_code, + error_message, + error_reason, + connector_response_reference_id, + }, + Self::UnresolvedResponseUpdate { + status, + connector, + connector_transaction_id, + payment_method_id, + error_code, + error_message, + error_reason, + connector_response_reference_id, + } => DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { + status, + connector, + connector_transaction_id, + payment_method_id, + error_code, + error_message, + error_reason, + connector_response_reference_id, + }, + Self::StatusUpdate { status } => DieselPaymentAttemptUpdate::StatusUpdate { status }, + Self::ErrorUpdate { + connector, + status, + error_code, + error_message, + error_reason, + } => DieselPaymentAttemptUpdate::ErrorUpdate { + connector, + status, + error_code, + error_message, + error_reason, + }, + Self::MultipleCaptureCountUpdate { + multiple_capture_count, + } => DieselPaymentAttemptUpdate::MultipleCaptureCountUpdate { + multiple_capture_count, + }, + Self::PreprocessingUpdate { + status, + payment_method_id, + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + } => DieselPaymentAttemptUpdate::PreprocessingUpdate { + status, + payment_method_id, + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + }, + Self::RejectUpdate { + status, + error_code, + error_message, + } => DieselPaymentAttemptUpdate::RejectUpdate { + status, + error_code, + error_message, + }, + } + } + + fn from_storage_model(storage_model: Self::StorageModel) -> Self { + match storage_model { + DieselPaymentAttemptUpdate::Update { + amount, + currency, + status, + authentication_type, + payment_method, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + amount_to_capture, + capture_method, + } => Self::Update { + amount, + currency, + status, + authentication_type, + payment_method, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + amount_to_capture, + capture_method, + }, + DieselPaymentAttemptUpdate::UpdateTrackers { + payment_token, + connector, + straight_through_algorithm, + } => Self::UpdateTrackers { + payment_token, + connector, + straight_through_algorithm, + }, + DieselPaymentAttemptUpdate::AuthenticationTypeUpdate { + authentication_type, + } => Self::AuthenticationTypeUpdate { + authentication_type, + }, + DieselPaymentAttemptUpdate::ConfirmUpdate { + amount, + currency, + status, + authentication_type, + payment_method, + browser_info, + connector, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + straight_through_algorithm, + error_code, + error_message, + } => Self::ConfirmUpdate { + amount, + currency, + status, + authentication_type, + payment_method, + browser_info, + connector, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + straight_through_algorithm, + error_code, + error_message, + }, + DieselPaymentAttemptUpdate::VoidUpdate { + status, + cancellation_reason, + } => Self::VoidUpdate { + status, + cancellation_reason, + }, + DieselPaymentAttemptUpdate::ResponseUpdate { + status, + connector, + connector_transaction_id, + authentication_type, + payment_method_id, + mandate_id, + connector_metadata, + payment_token, + error_code, + error_message, + error_reason, + connector_response_reference_id, + } => Self::ResponseUpdate { + status, + connector, + connector_transaction_id, + authentication_type, + payment_method_id, + mandate_id, + connector_metadata, + payment_token, + error_code, + error_message, + error_reason, + connector_response_reference_id, + }, + DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { + status, + connector, + connector_transaction_id, + payment_method_id, + error_code, + error_message, + error_reason, + connector_response_reference_id, + } => Self::UnresolvedResponseUpdate { + status, + connector, + connector_transaction_id, + payment_method_id, + error_code, + error_message, + error_reason, + connector_response_reference_id, + }, + DieselPaymentAttemptUpdate::StatusUpdate { status } => Self::StatusUpdate { status }, + DieselPaymentAttemptUpdate::ErrorUpdate { + connector, + status, + error_code, + error_message, + error_reason, + } => Self::ErrorUpdate { + connector, + status, + error_code, + error_message, + error_reason, + }, + DieselPaymentAttemptUpdate::MultipleCaptureCountUpdate { + multiple_capture_count, + } => Self::MultipleCaptureCountUpdate { + multiple_capture_count, + }, + DieselPaymentAttemptUpdate::PreprocessingUpdate { + status, + payment_method_id, + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + } => Self::PreprocessingUpdate { + status, + payment_method_id, + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + }, + DieselPaymentAttemptUpdate::RejectUpdate { + status, + error_code, + error_message, + } => Self::RejectUpdate { + status, + error_code, + error_message, + }, + } + } +} + +#[inline] +async fn add_connector_txn_id_to_reverse_lookup( + store: &RouterStore, + key: &str, + merchant_id: &str, + updated_attempt_attempt_id: &str, + connector_transaction_id: &str, +) -> CustomResult { + let conn = pg_connection_write(store).await?; + let field = format!("pa_{}", updated_attempt_attempt_id); + ReverseLookupNew { + lookup_id: format!("{}_{}", merchant_id, connector_transaction_id), + pk_id: key.to_owned(), + sk_id: field.clone(), + source: "payment_attempt".to_string(), + } + .insert(&conn) + .await + .map_err(|err| { + let new_err = crate::diesel_error_to_data_error(err.current_context()); + err.change_context(new_err) + }) +} + +#[inline] +async fn add_preprocessing_id_to_reverse_lookup( + store: &RouterStore, + key: &str, + merchant_id: &str, + updated_attempt_attempt_id: &str, + preprocessing_id: &str, +) -> CustomResult { + let conn = pg_connection_write(store).await?; + let field = format!("pa_{}", updated_attempt_attempt_id); + ReverseLookupNew { + lookup_id: format!("{}_{}", merchant_id, preprocessing_id), + pk_id: key.to_owned(), + sk_id: field.clone(), + source: "payment_attempt".to_string(), + } + .insert(&conn) + .await + .map_err(|er| { + let new_err = crate::diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + }) +} diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 8f20746ec0..eaac73cb6c 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -36,7 +36,7 @@ use router_env::logger; use crate::{ redis::kv_store::{PartitionKey, RedisConnInterface}, utils::{pg_connection_read, pg_connection_write}, - CustomResult, DataModelExt, DatabaseStore, KVRouterStore, MockDb, + DataModelExt, DatabaseStore, KVRouterStore, }; #[async_trait::async_trait] @@ -663,138 +663,6 @@ impl PaymentIntentInterface for crate::RouterStore { } } -#[async_trait::async_trait] -impl PaymentIntentInterface for MockDb { - #[cfg(feature = "olap")] - async fn filter_payment_intent_by_constraints( - &self, - _merchant_id: &str, - _filters: &PaymentIntentFetchConstraints, - _storage_scheme: MerchantStorageScheme, - ) -> CustomResult, StorageError> { - // [#172]: Implement function for `MockDb` - Err(StorageError::MockDbError)? - } - #[cfg(feature = "olap")] - async fn filter_payment_intents_by_time_range_constraints( - &self, - _merchant_id: &str, - _time_range: &api_models::payments::TimeRange, - _storage_scheme: MerchantStorageScheme, - ) -> CustomResult, StorageError> { - // [#172]: Implement function for `MockDb` - Err(StorageError::MockDbError)? - } - #[cfg(feature = "olap")] - async fn get_filtered_active_attempt_ids_for_total_count( - &self, - _merchant_id: &str, - _constraints: &PaymentIntentFetchConstraints, - _storage_scheme: MerchantStorageScheme, - ) -> error_stack::Result, StorageError> { - // [#172]: Implement function for `MockDb` - Err(StorageError::MockDbError)? - } - #[cfg(feature = "olap")] - async fn get_filtered_payment_intents_attempt( - &self, - _merchant_id: &str, - _constraints: &PaymentIntentFetchConstraints, - _storage_scheme: MerchantStorageScheme, - ) -> error_stack::Result, StorageError> { - // [#172]: Implement function for `MockDb` - Err(StorageError::MockDbError)? - } - - #[allow(clippy::panic)] - async fn insert_payment_intent( - &self, - new: PaymentIntentNew, - _storage_scheme: MerchantStorageScheme, - ) -> CustomResult { - let mut payment_intents = self.payment_intents.lock().await; - let time = common_utils::date_time::now(); - let payment_intent = PaymentIntent { - #[allow(clippy::as_conversions)] - id: payment_intents - .len() - .try_into() - .into_report() - .change_context(StorageError::MockDbError)?, - payment_id: new.payment_id, - merchant_id: new.merchant_id, - status: new.status, - amount: new.amount, - currency: new.currency, - amount_captured: new.amount_captured, - customer_id: new.customer_id, - description: new.description, - return_url: new.return_url, - metadata: new.metadata, - connector_id: new.connector_id, - shipping_address_id: new.shipping_address_id, - billing_address_id: new.billing_address_id, - statement_descriptor_name: new.statement_descriptor_name, - statement_descriptor_suffix: new.statement_descriptor_suffix, - created_at: new.created_at.unwrap_or(time), - modified_at: new.modified_at.unwrap_or(time), - last_synced: new.last_synced, - setup_future_usage: new.setup_future_usage, - off_session: new.off_session, - client_secret: new.client_secret, - business_country: new.business_country, - business_label: new.business_label, - active_attempt_id: new.active_attempt_id.to_owned(), - order_details: new.order_details, - allowed_payment_method_types: new.allowed_payment_method_types, - connector_metadata: new.connector_metadata, - feature_metadata: new.feature_metadata, - attempt_count: new.attempt_count, - profile_id: new.profile_id, - merchant_decision: new.merchant_decision, - payment_confirm_source: new.payment_confirm_source, - }; - payment_intents.push(payment_intent.clone()); - Ok(payment_intent) - } - - // safety: only used for testing - #[allow(clippy::unwrap_used)] - async fn update_payment_intent( - &self, - this: PaymentIntent, - update: PaymentIntentUpdate, - _storage_scheme: MerchantStorageScheme, - ) -> CustomResult { - let mut payment_intents = self.payment_intents.lock().await; - let payment_intent = payment_intents - .iter_mut() - .find(|item| item.id == this.id) - .unwrap(); - *payment_intent = update.apply_changeset(this); - Ok(payment_intent.clone()) - } - - // safety: only used for testing - #[allow(clippy::unwrap_used)] - async fn find_payment_intent_by_payment_id_merchant_id( - &self, - payment_id: &str, - merchant_id: &str, - _storage_scheme: MerchantStorageScheme, - ) -> CustomResult { - let payment_intents = self.payment_intents.lock().await; - - Ok(payment_intents - .iter() - .find(|payment_intent| { - payment_intent.payment_id == payment_id && payment_intent.merchant_id == merchant_id - }) - .cloned() - .unwrap()) - } -} - impl DataModelExt for PaymentIntentNew { type StorageModel = DieselPaymentIntentNew; diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index 7d4edaca13..8697482682 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -1,11 +1,19 @@ use std::{any::Any, borrow::Cow, sync::Arc}; -use common_utils::errors; +use common_utils::{ + errors::{self, CustomResult}, + ext_traits::AsyncExt, +}; +use data_models::errors::StorageError; use dyn_clone::DynClone; -use error_stack::Report; +use error_stack::{Report, ResultExt}; use moka::future::Cache as MokaCache; use once_cell::sync::Lazy; -use redis_interface::RedisValue; +use redis_interface::{errors::RedisError, RedisValue}; + +use super::{kv_store::RedisConnInterface, pub_sub::PubSubInterface}; + +pub(crate) const PUB_SUB_CHANNEL: &str = "hyperswitch_invalidate"; /// Prefix for config cache key const CONFIG_CACHE_PREFIX: &str = "config"; @@ -128,6 +136,127 @@ impl Cache { } } +pub async fn get_or_populate_redis( + store: &(dyn RedisConnInterface + Send + Sync), + key: &str, + fun: F, +) -> CustomResult +where + T: serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug, + F: FnOnce() -> Fut + Send, + Fut: futures::Future> + Send, +{ + let type_name = std::any::type_name::(); + let redis = &store + .get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(StorageError::RedisError(error)) + }) + .attach_printable("Failed to get redis connection")?; + let redis_val = redis.get_and_deserialize_key::(key, type_name).await; + let get_data_set_redis = || async { + let data = fun().await?; + redis + .serialize_and_set_key(key, &data) + .await + .change_context(StorageError::KVError)?; + Ok::<_, Report>(data) + }; + match redis_val { + Err(err) => match err.current_context() { + RedisError::NotFound | RedisError::JsonDeserializationFailed => { + get_data_set_redis().await + } + _ => Err(err + .change_context(StorageError::KVError) + .attach_printable(format!("Error while fetching cache for {type_name}"))), + }, + Ok(val) => Ok(val), + } +} + +pub async fn get_or_populate_in_memory( + store: &(dyn RedisConnInterface + Send + Sync), + key: &str, + fun: F, + cache: &Cache, +) -> CustomResult +where + T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug + Clone, + F: FnOnce() -> Fut + Send, + Fut: futures::Future> + Send, +{ + let cache_val = cache.get_val::(key); + if let Some(val) = cache_val { + Ok(val) + } else { + let val = get_or_populate_redis(store, key, fun).await?; + cache.push(key.to_string(), val.clone()).await; + Ok(val) + } +} + +pub async fn redact_cache( + store: &dyn RedisConnInterface, + key: &str, + fun: F, + in_memory: Option<&Cache>, +) -> CustomResult +where + F: FnOnce() -> Fut + Send, + Fut: futures::Future> + Send, +{ + let data = fun().await?; + in_memory.async_map(|cache| cache.invalidate(key)).await; + + let redis_conn = store + .get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(StorageError::RedisError(error)) + }) + .attach_printable("Failed to get redis connection")?; + + redis_conn + .delete_key(key) + .await + .change_context(StorageError::KVError)?; + Ok(data) +} + +pub async fn publish_into_redact_channel<'a>( + store: &dyn RedisConnInterface, + key: CacheKind<'a>, +) -> CustomResult { + let redis_conn = store + .get_redis_conn() + .map_err(|er| { + let error = format!("{}", er); + er.change_context(StorageError::RedisError(error)) + }) + .attach_printable("Failed to get redis connection")?; + + redis_conn + .publish(PUB_SUB_CHANNEL, key) + .await + .change_context(StorageError::KVError) +} + +pub async fn publish_and_redact<'a, T, F, Fut>( + store: &dyn RedisConnInterface, + key: CacheKind<'a>, + fun: F, +) -> CustomResult +where + F: FnOnce() -> Fut + Send, + Fut: futures::Future> + Send, +{ + let data = fun().await?; + publish_into_redact_channel(store, key).await?; + Ok(data) +} + #[cfg(test)] mod cache_tests { use super::*; diff --git a/crates/storage_impl/src/utils.rs b/crates/storage_impl/src/utils.rs index 6d6e1cd540..92fd11debe 100644 --- a/crates/storage_impl/src/utils.rs +++ b/crates/storage_impl/src/utils.rs @@ -68,3 +68,13 @@ where }, } } + +/// Generates hscan field pattern. Suppose the field is pa_1234 it will generate +/// pa_* +pub fn generate_hscan_pattern_for_attempt(sk: &str) -> String { + sk.split('_') + .take(1) + .chain(["*"]) + .collect::>() + .join("_") +}