diff --git a/Cargo.lock b/Cargo.lock index 9d13e896cb..192e6cc349 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -916,9 +916,11 @@ dependencies = [ name = "common_utils" version = "0.1.0" dependencies = [ + "async-trait", "bytes", "error-stack", "fake", + "futures", "hex", "masking", "nanoid", diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 643b14c37c..e63611c4ad 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -130,6 +130,21 @@ pub enum MandateTxnType { RecurringMandateTxn, } +#[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct MandateIds { + pub mandate_id: String, + pub connector_mandate_id: Option, +} + +impl MandateIds { + pub fn new(mandate_id: String) -> Self { + Self { + mandate_id, + connector_mandate_id: None, + } + } +} + #[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] #[serde(deny_unknown_fields)] pub struct MandateData { diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index 5c1f4b8230..192772b859 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -8,8 +8,10 @@ readme = "README.md" license = "Apache-2.0" [dependencies] +async-trait = "0.1.59" bytes = "1.3.0" error-stack = "0.2.4" +futures = "0.3.25" hex = "0.4.3" nanoid = "0.4.0" once_cell = "1.16.0" diff --git a/crates/common_utils/src/ext_traits.rs b/crates/common_utils/src/ext_traits.rs index 83770e6ca8..808381cd3e 100644 --- a/crates/common_utils/src/ext_traits.rs +++ b/crates/common_utils/src/ext_traits.rs @@ -277,3 +277,79 @@ impl StringExt for String { .attach_printable_lazy(|| format!("Unable to parse {type_name} from string")) } } + +/// +/// Extending functionalities of Wrapper types for idiomatic +/// +#[async_trait::async_trait] +pub trait AsyncExt { + /// Output type of the map function + type WrappedSelf; + /// + /// Extending map by allowing functions which are async + /// + async fn async_map(self, func: F) -> Self::WrappedSelf + where + F: FnOnce(A) -> Fut + Send, + Fut: futures::Future + Send; + + /// + /// Extending the `and_then` by allowing functions which are async + /// + async fn async_and_then(self, func: F) -> Self::WrappedSelf + where + F: FnOnce(A) -> Fut + Send, + Fut: futures::Future> + Send; +} + +#[async_trait::async_trait] +impl AsyncExt for Result { + type WrappedSelf = Result; + async fn async_and_then(self, func: F) -> Self::WrappedSelf + where + F: FnOnce(A) -> Fut + Send, + Fut: futures::Future> + Send, + { + match self { + Ok(a) => func(a).await, + Err(err) => Err(err), + } + } + + async fn async_map(self, func: F) -> Self::WrappedSelf + where + F: FnOnce(A) -> Fut + Send, + Fut: futures::Future + Send, + { + match self { + Ok(a) => Ok(func(a).await), + Err(err) => Err(err), + } + } +} + +#[async_trait::async_trait] +impl AsyncExt for Option { + type WrappedSelf = Option; + async fn async_and_then(self, func: F) -> Self::WrappedSelf + where + F: FnOnce(A) -> Fut + Send, + Fut: futures::Future> + Send, + { + match self { + Some(a) => func(a).await, + None => None, + } + } + + async fn async_map(self, func: F) -> Self::WrappedSelf + where + F: FnOnce(A) -> Fut + Send, + Fut: futures::Future + Send, + { + match self { + Some(a) => Some(func(a).await), + None => None, + } + } +} diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index b59fe6867e..8db92ba8b5 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -90,7 +90,6 @@ pub struct PaymentIntentRequest { #[derive(Debug, Eq, PartialEq, Serialize)] pub struct SetupIntentRequest { - pub statement_descriptor_suffix: Option, #[serde(rename = "metadata[order_id]")] pub metadata_order_id: String, #[serde(rename = "metadata[txn_id]")] @@ -98,7 +97,7 @@ pub struct SetupIntentRequest { #[serde(rename = "metadata[txn_uuid]")] pub metadata_txn_uuid: String, pub confirm: bool, - pub setup_future_usage: Option, + pub usage: Option, pub off_session: Option, #[serde(flatten)] pub payment_data: StripePaymentMethodData, @@ -153,7 +152,12 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { // let api::PaymentMethod::Card(a) = item.payment_method_data; let (payment_data, mandate) = { - match item.request.mandate_id.clone() { + match item + .request + .mandate_id + .clone() + .and_then(|mandate_ids| mandate_ids.connector_mandate_id) + { None => ( Some(match item.request.payment_method_data { api::PaymentMethod::Card(ref ccard) => StripePaymentMethodData::Card({ @@ -257,9 +261,8 @@ impl TryFrom<&types::VerifyRouterData> for SetupIntentRequest { metadata_txn_id, metadata_txn_uuid, payment_data, - statement_descriptor_suffix: item.request.statement_descriptor_suffix.clone(), off_session: item.request.off_session, - setup_future_usage: item.request.setup_future_usage, + usage: item.request.setup_future_usage, }) } } diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index 014c70e4fd..c087421c41 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -93,6 +93,7 @@ where { match resp.request.get_mandate_id() { Some(mandate_id) => { + let mandate_id = &mandate_id.mandate_id; let mandate = state .store .find_mandate_by_merchant_id_mandate_id(resp.merchant_id.as_ref(), mandate_id) @@ -157,7 +158,10 @@ where mandate_reference, ) { resp.request - .set_mandate_id(new_mandate_data.mandate_id.clone()); + .set_mandate_id(api_models::payments::MandateIds { + mandate_id: new_mandate_data.mandate_id.clone(), + connector_mandate_id: new_mandate_data.connector_mandate_id.clone(), + }); state .store .insert_mandate(new_mandate_data) @@ -178,7 +182,7 @@ where pub trait MandateBehaviour { fn get_amount(&self) -> i64; fn get_setup_future_usage(&self) -> Option; - fn get_mandate_id(&self) -> Option<&String>; - fn set_mandate_id(&mut self, new_mandate_id: String); + fn get_mandate_id(&self) -> Option<&api_models::payments::MandateIds>; + fn set_mandate_id(&mut self, new_mandate_id: api_models::payments::MandateIds); fn get_payment_method_data(&self) -> api_models::payments::PaymentMethod; } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 2e19674e86..9d8af0fffe 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5,6 +5,7 @@ pub mod transformers; use std::{fmt::Debug, marker::PhantomData, time::Instant}; +use common_utils::ext_traits::AsyncExt; use error_stack::{IntoReport, ResultExt}; use futures::future::join_all; use router_env::{tracing, tracing::instrument}; @@ -61,6 +62,7 @@ where // To perform router related operation for PaymentResponse PaymentResponse: Operation, + FData: std::marker::Send, { let operation: BoxedOperation = Box::new(operation); @@ -181,6 +183,7 @@ pub async fn payments_core( ) -> RouterResponse where F: Send + Clone, + FData: std::marker::Send, Op: Operation + Send + Sync + Clone, Req: Debug, Res: transformers::ToResponse, Op> + From, @@ -194,7 +197,6 @@ where // To perform router related operation for PaymentResponse PaymentResponse: Operation, - // To create merchant response { let (payment_data, req, customer) = payments_operation_core( state, @@ -313,7 +315,7 @@ where // To create connector flow specific interface data PaymentData: ConstructFlowSpecificData, - types::RouterData: Feature, + types::RouterData: Feature + Send, // To construct connector flow specific api dyn api::Connector: services::api::ConnectorIntegration, @@ -339,21 +341,22 @@ where ) .await; - let response = helpers::amap(res, |response| async { - let operation = helpers::response_operation::(); - let payment_data = operation - .to_post_update_tracker()? - .update_tracker( - db, - payment_id, - payment_data, - Some(response), - merchant_account.storage_scheme, - ) - .await?; - Ok(payment_data) - }) - .await?; + let response = res + .async_and_then(|response| async { + let operation = helpers::response_operation::(); + let payment_data = operation + .to_post_update_tracker()? + .update_tracker( + db, + payment_id, + payment_data, + response, + merchant_account.storage_scheme, + ) + .await?; + Ok(payment_data) + }) + .await?; let etime_connector = Instant::now(); let duration_connector = etime_connector.saturating_duration_since(stime_connector); @@ -457,8 +460,8 @@ where pub payment_attempt: storage::PaymentAttempt, pub connector_response: storage::ConnectorResponse, pub amount: api::Amount, + pub mandate_id: Option, pub currency: storage_enums::Currency, - pub mandate_id: Option, pub setup_mandate: Option, pub address: PaymentAddress, pub token: Option, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index f590c280e5..d8a80230c5 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -111,7 +111,7 @@ impl mandate::MandateBehaviour for types::PaymentsAuthorizeData { fn get_amount(&self) -> i64 { self.amount } - fn get_mandate_id(&self) -> Option<&String> { + fn get_mandate_id(&self) -> Option<&api_models::payments::MandateIds> { self.mandate_id.as_ref() } fn get_payment_method_data(&self) -> api_models::payments::PaymentMethod { @@ -120,7 +120,7 @@ impl mandate::MandateBehaviour for types::PaymentsAuthorizeData { fn get_setup_future_usage(&self) -> Option { self.setup_future_usage } - fn set_mandate_id(&mut self, new_mandate_id: String) { + fn set_mandate_id(&mut self, new_mandate_id: api_models::payments::MandateIds) { self.mandate_id = Some(new_mandate_id); } } diff --git a/crates/router/src/core/payments/flows/verfiy_flow.rs b/crates/router/src/core/payments/flows/verfiy_flow.rs index c5c51ea9cd..bf7805d020 100644 --- a/crates/router/src/core/payments/flows/verfiy_flow.rs +++ b/crates/router/src/core/payments/flows/verfiy_flow.rs @@ -98,11 +98,11 @@ impl mandate::MandateBehaviour for types::VerifyRequestData { self.setup_future_usage } - fn get_mandate_id(&self) -> Option<&String> { + fn get_mandate_id(&self) -> Option<&api_models::payments::MandateIds> { self.mandate_id.as_ref() } - fn set_mandate_id(&mut self, new_mandate_id: String) { + fn set_mandate_id(&mut self, new_mandate_id: api_models::payments::MandateIds) { self.mandate_id = Some(new_mandate_id); } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 53ca72ae37..7b0487ac0b 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -390,7 +390,7 @@ pub fn verify_mandate_details( mandate .mandate_currency .map(|mandate_currency| mandate_currency.to_string() != request_currency) - .unwrap_or(true), + .unwrap_or(false), Err(report!(errors::ApiErrorResponse::MandateValidationFailed { reason: "cross currency mandates not supported".to_string() })), @@ -471,17 +471,6 @@ where Box::new(PaymentResponse) } -pub async fn amap(value: Result, func: F) -> Result -where - F: FnOnce(A) -> Fut, - Fut: futures::Future>, -{ - match value { - Ok(a) => func(a).await, - Err(err) => Err(err), - } -} - #[instrument(skip_all)] pub(crate) async fn call_payment_method( state: &AppState, diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index cf7a20a41b..81bf3fe55d 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -164,7 +164,7 @@ pub trait PostUpdateTracker: Send { db: &dyn StorageInterface, payment_id: &api::PaymentIdType, payment_data: D, - response: Option>, + response: types::RouterData, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult where diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 654f9b0d35..196ec19dff 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; use async_trait::async_trait; +use common_utils::ext_traits::AsyncExt; use error_stack::ResultExt; use masking::Secret; use router_derive::PaymentOperation; @@ -182,6 +183,22 @@ impl GetTracker, api::PaymentsRequest> for Pa } }?; + let mandate_id = request + .mandate_id + .as_ref() + .async_and_then(|mandate_id| async { + let mandate = db + .find_mandate_by_merchant_id_mandate_id(merchant_id, mandate_id) + .await + .change_context(errors::ApiErrorResponse::MandateNotFound); + Some(mandate.map(|mandate_obj| api_models::payments::MandateIds { + mandate_id: mandate_obj.mandate_id, + connector_mandate_id: mandate_obj.connector_mandate_id, + })) + }) + .await + .transpose()?; + let operation = payments::if_not_create_change_operation::<_, F>( is_update, payment_intent.status, @@ -197,7 +214,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_attempt, currency, amount, - mandate_id: request.mandate_id.clone(), + mandate_id, setup_mandate, token, address: PaymentAddress { diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 9b86343a49..3e008585b0 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use router_derive; use super::{Operation, PostUpdateTracker}; @@ -34,27 +34,22 @@ impl PostUpdateTracker, types::PaymentsAuthorizeData db: &dyn StorageInterface, payment_id: &api::PaymentIdType, mut payment_data: PaymentData, - response: Option< - types::RouterData, + router_data: types::RouterData< + F, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, >, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where F: 'b + Send, { - let router_data = response.ok_or(report!(errors::ApiErrorResponse::InternalServerError))?; payment_data.mandate_id = payment_data .mandate_id .or_else(|| router_data.request.mandate_id.clone()); - payment_response_update_tracker( - db, - payment_id, - payment_data, - Some(router_data), - storage_scheme, - ) - .await + payment_response_update_tracker(db, payment_id, payment_data, router_data, storage_scheme) + .await } } @@ -65,9 +60,7 @@ impl PostUpdateTracker, types::PaymentsSyncData> for db: &dyn StorageInterface, payment_id: &api::PaymentIdType, payment_data: PaymentData, - response: Option< - types::RouterData, - >, + response: types::RouterData, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -87,9 +80,7 @@ impl PostUpdateTracker, types::PaymentsSessionData> db: &dyn StorageInterface, payment_id: &api::PaymentIdType, payment_data: PaymentData, - response: Option< - types::RouterData, - >, + response: types::RouterData, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -109,9 +100,7 @@ impl PostUpdateTracker, types::PaymentsCaptureData> db: &dyn StorageInterface, payment_id: &api::PaymentIdType, payment_data: PaymentData, - response: Option< - types::RouterData, - >, + response: types::RouterData, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -129,9 +118,8 @@ impl PostUpdateTracker, types::PaymentsCancelData> f db: &dyn StorageInterface, payment_id: &api::PaymentIdType, payment_data: PaymentData, - response: Option< - types::RouterData, - >, + response: types::RouterData, + storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -148,16 +136,20 @@ impl PostUpdateTracker, types::VerifyRequestData> fo &'b self, db: &dyn StorageInterface, payment_id: &api::PaymentIdType, - payment_data: PaymentData, - response: Option< - types::RouterData, - >, + mut payment_data: PaymentData, + router_data: types::RouterData, + storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where F: 'b + Send, { - payment_response_update_tracker(db, payment_id, payment_data, response, storage_scheme) + payment_data.mandate_id = payment_data.mandate_id.or_else(|| { + router_data.request.mandate_id.clone() + // .map(api_models::payments::MandateIds::new) + }); + + payment_response_update_tracker(db, payment_id, payment_data, router_data, storage_scheme) .await } } @@ -166,11 +158,9 @@ async fn payment_response_update_tracker( db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, - response: Option>, + router_data: types::RouterData, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> { - let router_data = response.ok_or(report!(errors::ApiErrorResponse::InternalServerError))?; - let (payment_attempt_update, connector_response_update) = match router_data.response.clone() { Err(err) => ( Some(storage::PaymentAttemptUpdate::ErrorUpdate { @@ -212,7 +202,10 @@ async fn payment_response_update_tracker( authentication_type: None, payment_method_id: Some(router_data.payment_method_id), redirect: Some(redirect), - mandate_id: payment_data.mandate_id.clone(), + mandate_id: payment_data + .mandate_id + .clone() + .map(|mandate| mandate.mandate_id), }; let connector_response_update = storage::ConnectorResponseUpdate::ResponseUpdate { diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index a319e05e2a..129ba54fe9 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; use async_trait::async_trait; +use common_utils::ext_traits::AsyncExt; use error_stack::{report, ResultExt}; use masking::Secret; use router_derive::PaymentOperation; @@ -126,6 +127,21 @@ impl GetTracker, api::PaymentsRequest> for Pa .attach_printable("Database error when finding connector response") })?; + let mandate_id = request + .mandate_id + .as_ref() + .async_and_then(|mandate_id| async { + let mandate = db + .find_mandate_by_merchant_id_mandate_id(merchant_id, mandate_id) + .await + .change_context(errors::ApiErrorResponse::MandateNotFound); + Some(mandate.map(|mandate_obj| api_models::payments::MandateIds { + mandate_id: mandate_obj.mandate_id, + connector_mandate_id: mandate_obj.connector_mandate_id, + })) + }) + .await + .transpose()?; let next_operation: BoxedOperation<'a, F, api::PaymentsRequest> = if request.confirm.unwrap_or(false) { Box::new(operations::PaymentConfirm) @@ -149,7 +165,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_attempt, currency, amount, - mandate_id: request.mandate_id.clone(), + mandate_id, token, setup_mandate, address: PaymentAddress { diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 5118ceab55..694d29f896 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -197,7 +197,7 @@ where phone: customer .as_ref() .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())), - mandate_id: data.mandate_id, + mandate_id: data.mandate_id.map(|mandate_ids| mandate_ids.mandate_id), payment_method: data .payment_attempt .payment_method @@ -499,8 +499,8 @@ impl TryFrom> for types::VerifyRequestData { }, statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix, setup_future_usage: payment_data.payment_intent.setup_future_usage, - mandate_id: payment_data.mandate_id.clone(), off_session: payment_data.mandate_id.as_ref().map(|_| true), + mandate_id: payment_data.mandate_id.clone(), setup_mandate_details: payment_data.setup_mandate, }) } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index f991c4415c..c8d831e51c 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -97,7 +97,7 @@ pub struct PaymentsAuthorizeData { pub capture_method: Option, // Mandates pub setup_future_usage: Option, - pub mandate_id: Option, + pub mandate_id: Option, pub off_session: Option, pub setup_mandate_details: Option, pub browser_info: Option, @@ -134,7 +134,7 @@ pub struct VerifyRequestData { pub payment_method_data: payments::PaymentMethod, pub confirm: bool, pub statement_descriptor_suffix: Option, - pub mandate_id: Option, + pub mandate_id: Option, pub setup_future_usage: Option, pub off_session: Option, pub setup_mandate_details: Option,