diff --git a/CHANGELOG.md b/CHANGELOG.md index 781e38dff833..4c77f46c233b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.10.18.0 + +### Features + +- **router:** Add payments create-intent flow for v2 ([#6193](https://github.com/juspay/hyperswitch/pull/6193)) ([`afa803e`](https://github.com/juspay/hyperswitch/commit/afa803e0f9711f83b31ce53a59e867517a885963)) +- **worldpay:** Migrate to v7 ([#6109](https://github.com/juspay/hyperswitch/pull/6109)) ([`962afbd`](https://github.com/juspay/hyperswitch/commit/962afbd084458e9afb11a0278a8210edd9226a3d)) + +### Bug Fixes + +- **mandates:** Handle the connector_mandate creation once and only if the payment is charged ([#6327](https://github.com/juspay/hyperswitch/pull/6327)) ([`e14a0fe`](https://github.com/juspay/hyperswitch/commit/e14a0fe8f290a697126756ba2facc58234e5d135)) +- **payments_list:** Skip count query if no filters and add logging ([#6331](https://github.com/juspay/hyperswitch/pull/6331)) ([`df2501c`](https://github.com/juspay/hyperswitch/commit/df2501ceafab6180e867953f7c298a541fcea757)) +- **router:** Set the eligible connector in the payment attempt for nti based mit flow ([#6347](https://github.com/juspay/hyperswitch/pull/6347)) ([`1a3d0a6`](https://github.com/juspay/hyperswitch/commit/1a3d0a60f4e3b07786460621c14c5aa37510b53a)) +- **users:** Add max wrong attempts for two-fa ([#6247](https://github.com/juspay/hyperswitch/pull/6247)) ([`2798f57`](https://github.com/juspay/hyperswitch/commit/2798f575605cc4439166344e57ff19b612f1304a)) +- Set headers as optional in ob flows ([#6305](https://github.com/juspay/hyperswitch/pull/6305)) ([`9576ee3`](https://github.com/juspay/hyperswitch/commit/9576ee37a6468d79afc4be280749a2176a95e63b)) + +**Full Changelog:** [`2024.10.17.0...2024.10.18.0`](https://github.com/juspay/hyperswitch/compare/2024.10.17.0...2024.10.18.0) + +- - - + ## 2024.10.17.0 ### Features diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 8306deeb258c..e36a6d74994e 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3356,3 +3356,16 @@ pub enum SurchargeCalculationOverride { /// Calculate surcharge Calculate, } + +/// Connector Mandate Status +#[derive( + Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, strum::Display, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum ConnectorMandateStatus { + /// Indicates that the connector mandate is active and can be used for payments. + Active, + /// Indicates that the connector mandate is not active and hence cannot be used for payments. + Inactive, +} diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 277061c78aa7..ae98b8a04815 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -12,6 +12,16 @@ use crate::schema::payment_attempt; #[cfg(feature = "v2")] use crate::schema_v2::payment_attempt; +common_utils::impl_to_sql_from_sql_json!(ConnectorMandateReferenceId); +#[derive( + Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq, diesel::AsExpression, +)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct ConnectorMandateReferenceId { + pub connector_mandate_id: Option, + pub payment_method_id: Option, + pub mandate_metadata: Option, +} #[cfg(feature = "v2")] #[derive( Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Serialize, Deserialize, Selectable, @@ -76,6 +86,7 @@ pub struct PaymentAttempt { pub id: String, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -153,6 +164,7 @@ pub struct PaymentAttempt { pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_transaction_data: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -256,6 +268,7 @@ pub struct PaymentAttemptNew { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -328,6 +341,7 @@ pub struct PaymentAttemptNew { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -442,6 +456,7 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, payment_method_data: Option, charge_id: Option, + connector_mandate_detail: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -757,6 +772,7 @@ pub struct PaymentAttemptUpdateInternal { customer_acceptance: Option, card_network: Option, connector_payment_data: Option, + connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -813,6 +829,7 @@ pub struct PaymentAttemptUpdateInternal { pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_transaction_data: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v2")] @@ -1002,6 +1019,7 @@ impl PaymentAttemptUpdate { shipping_cost, order_tax_amount, connector_transaction_data, + connector_mandate_detail, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -1059,6 +1077,7 @@ impl PaymentAttemptUpdate { order_tax_amount: order_tax_amount.or(source.order_tax_amount), connector_transaction_data: connector_transaction_data .or(source.connector_transaction_data), + connector_mandate_detail: connector_mandate_detail.or(source.connector_mandate_detail), ..source } } @@ -2054,6 +2073,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::AuthenticationTypeUpdate { authentication_type, @@ -2109,6 +2129,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::ConfirmUpdate { amount, @@ -2194,6 +2215,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost, order_tax_amount, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::VoidUpdate { status, @@ -2250,6 +2272,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::RejectUpdate { status, @@ -2307,6 +2330,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::BlocklistUpdate { status, @@ -2364,6 +2388,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::PaymentMethodDetailsUpdate { payment_method_id, @@ -2419,6 +2444,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::ResponseUpdate { status, @@ -2441,6 +2467,7 @@ impl From for PaymentAttemptUpdateInternal { unified_message, payment_method_data, charge_id, + connector_mandate_detail, } => { let (connector_transaction_id, connector_transaction_data) = connector_transaction_id @@ -2498,6 +2525,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_mandate_detail, } } PaymentAttemptUpdate::ErrorUpdate { @@ -2570,6 +2598,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_mandate_detail: None, } } PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { @@ -2623,6 +2652,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::UpdateTrackers { payment_token, @@ -2684,6 +2714,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -2752,6 +2783,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_mandate_detail: None, } } PaymentAttemptUpdate::PreprocessingUpdate { @@ -2819,6 +2851,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_mandate_detail: None, } } PaymentAttemptUpdate::CaptureUpdate { @@ -2876,6 +2909,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::AmountToCaptureUpdate { status, @@ -2932,6 +2966,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::ConnectorResponse { authentication_data, @@ -2997,6 +3032,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_mandate_detail: None, } } PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { @@ -3053,6 +3089,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::AuthenticationUpdate { status, @@ -3111,6 +3148,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::ManualUpdate { status, @@ -3178,6 +3216,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_mandate_detail: None, } } PaymentAttemptUpdate::PostSessionTokensUpdate { @@ -3234,6 +3273,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_transaction_data: None, + connector_mandate_detail: None, }, } } @@ -3319,7 +3359,6 @@ mod tests { "user_agent": "amet irure esse" } }, - "mandate_type": { "single_use": { "amount": 6540, "currency": "USD" diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 5cd2abd35993..8d2dfc47b623 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -850,6 +850,7 @@ diesel::table! { order_tax_amount -> Nullable, #[max_length = 512] connector_transaction_data -> Nullable, + connector_mandate_detail -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index d662603981ba..45a4ce03e55b 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -821,6 +821,7 @@ diesel::table! { id -> Varchar, shipping_cost -> Nullable, order_tax_amount -> Nullable, + connector_mandate_detail -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index db30d02bd7cd..88617afb3925 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -12,7 +12,7 @@ use crate::schema::payment_attempt; use crate::schema_v2::payment_attempt; use crate::{ enums::{MandateDataType, MandateDetails}, - PaymentAttemptNew, + ConnectorMandateReferenceId, PaymentAttemptNew, }; #[cfg(feature = "v2")] @@ -204,6 +204,7 @@ pub struct PaymentAttemptBatchNew { pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_transaction_data: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -282,6 +283,7 @@ impl PaymentAttemptBatchNew { organization_id: self.organization_id, shipping_cost: self.shipping_cost, order_tax_amount: self.order_tax_amount, + connector_mandate_detail: self.connector_mandate_detail, } } } diff --git a/crates/hyperswitch_connectors/src/connectors/mollie.rs b/crates/hyperswitch_connectors/src/connectors/mollie.rs index 5066fb422faf..010ae90c56f1 100644 --- a/crates/hyperswitch_connectors/src/connectors/mollie.rs +++ b/crates/hyperswitch_connectors/src/connectors/mollie.rs @@ -1,12 +1,11 @@ pub mod transformers; -use std::fmt::Debug; - use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -43,10 +42,20 @@ use masking::{Mask, PeekInterface}; use transformers as mollie; // use self::mollie::{webhook_headers, VoltWebhookBodyEventType}; -use crate::{constants::headers, types::ResponseRouterData}; +use crate::{constants::headers, types::ResponseRouterData, utils::convert_amount}; + +#[derive(Clone)] +pub struct Mollie { + amount_converter: &'static (dyn AmountConvertor + Sync), +} -#[derive(Debug, Clone)] -pub struct Mollie; +impl Mollie { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMajorUnitForConnector, + } + } +} impl api::Payment for Mollie {} impl api::PaymentSession for Mollie {} @@ -254,12 +263,13 @@ impl ConnectorIntegration CustomResult { - let router_obj = mollie::MollieRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + + let router_obj = mollie::MollieRouterData::from((amount, req)); let connector_req = mollie::MolliePaymentsRequest::try_from(&router_obj)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -448,12 +458,13 @@ impl ConnectorIntegration for Mollie req: &RefundsRouterData, _connectors: &Connectors, ) -> CustomResult { - let router_obj = mollie::MollieRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_refund_amount, req.request.currency, - req.request.refund_amount, - req, - ))?; + )?; + + let router_obj = mollie::MollieRouterData::from((amount, req)); let connector_req = mollie::MollieRefundRequest::try_from(&router_obj)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs b/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs index a12434a460a7..dc5f64bee184 100644 --- a/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs @@ -1,6 +1,6 @@ use cards::CardNumber; use common_enums::enums; -use common_utils::{pii::Email, request::Method}; +use common_utils::{pii::Email, request::Method, types::StringMajorUnit}; use hyperswitch_domain_models::{ payment_method_data::{BankDebitData, BankRedirectData, PaymentMethodData, WalletData}, router_data::{ConnectorAuthType, PaymentMethodToken, RouterData}, @@ -8,7 +8,7 @@ use hyperswitch_domain_models::{ router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, types, }; -use hyperswitch_interfaces::{api, errors}; +use hyperswitch_interfaces::errors; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use url::Url; @@ -17,7 +17,7 @@ use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, unimplemented_payment_method, utils::{ - self, AddressDetailsData, BrowserInformationData, CardData as CardDataUtil, + AddressDetailsData, BrowserInformationData, CardData as CardDataUtil, PaymentMethodTokenizationRequestData, PaymentsAuthorizeRequestData, RouterData as _, }, }; @@ -26,26 +26,16 @@ type Error = error_stack::Report; #[derive(Debug, Serialize)] pub struct MollieRouterData { - pub amount: String, + pub amount: StringMajorUnit, pub router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for MollieRouterData { - type Error = error_stack::Report; - - fn try_from( - (currency_unit, currency, amount, router_data): ( - &api::CurrencyUnit, - enums::Currency, - i64, - T, - ), - ) -> Result { - let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; - Ok(Self { +impl From<(StringMajorUnit, T)> for MollieRouterData { + fn from((amount, router_data): (StringMajorUnit, T)) -> Self { + Self { amount, router_data, - }) + } } } @@ -68,7 +58,7 @@ pub struct MolliePaymentsRequest { #[derive(Default, Debug, Serialize, Deserialize)] pub struct Amount { currency: enums::Currency, - value: String, + value: StringMajorUnit, } #[derive(Debug, Serialize)] diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 691037c2824c..8ecac77e54e4 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -10,7 +10,8 @@ use common_utils::{ }, }; use diesel_models::{ - PaymentAttempt as DieselPaymentAttempt, PaymentAttemptNew as DieselPaymentAttemptNew, + ConnectorMandateReferenceId, PaymentAttempt as DieselPaymentAttempt, + PaymentAttemptNew as DieselPaymentAttemptNew, PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; use error_stack::ResultExt; @@ -222,6 +223,7 @@ pub struct PaymentAttempt { pub shipping_cost: Option, pub order_tax_amount: Option, pub id: String, + pub connector_mandate_detail: Option, } impl PaymentAttempt { @@ -331,6 +333,7 @@ pub struct PaymentAttempt { pub customer_acceptance: Option, pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, + pub connector_mandate_detail: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)] @@ -624,6 +627,7 @@ pub struct PaymentAttemptNew { pub customer_acceptance: Option, pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -732,6 +736,7 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, payment_method_data: Option, charge_id: Option, + connector_mandate_detail: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -992,6 +997,7 @@ impl PaymentAttemptUpdate { unified_message, payment_method_data, charge_id, + connector_mandate_detail, } => DieselPaymentAttemptUpdate::ResponseUpdate { status, connector, @@ -1013,6 +1019,7 @@ impl PaymentAttemptUpdate { unified_message, payment_method_data, charge_id, + connector_mandate_detail, }, Self::UnresolvedResponseUpdate { status, @@ -1281,6 +1288,7 @@ impl behaviour::Conversion for PaymentAttempt { connector_transaction_data, order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), + connector_mandate_detail: self.connector_mandate_detail, }) } @@ -1361,6 +1369,7 @@ impl behaviour::Conversion for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, profile_id: storage_model.profile_id, organization_id: storage_model.organization_id, + connector_mandate_detail: storage_model.connector_mandate_detail, }) } .await @@ -1442,6 +1451,7 @@ impl behaviour::Conversion for PaymentAttempt { card_network, order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), + connector_mandate_detail: self.connector_mandate_detail, }) } } @@ -1517,6 +1527,7 @@ impl behaviour::Conversion for PaymentAttempt { shipping_cost, order_tax_amount, connector, + connector_mandate_detail, } = self; let (connector_payment_id, connector_payment_data) = connector_payment_id @@ -1580,6 +1591,7 @@ impl behaviour::Conversion for PaymentAttempt { external_reference_id, connector, connector_payment_data, + connector_mandate_detail, }) } @@ -1651,6 +1663,7 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied: storage_model.authentication_applied, external_reference_id: storage_model.external_reference_id, connector: storage_model.connector, + connector_mandate_detail: storage_model.connector_mandate_detail, }) } .await @@ -1718,6 +1731,7 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.order_tax_amount, shipping_cost: self.shipping_cost, amount_to_capture: self.amount_to_capture, + connector_mandate_detail: self.connector_mandate_detail, }) } } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index fc747bfa5914..fd45c475b6d5 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4318,6 +4318,10 @@ where } }; + // Set the eligible connector in the attempt + payment_data + .set_connector_in_payment_attempt(Some(eligible_connector_data.connector_name.to_string())); + // Set `NetworkMandateId` as the MandateId payment_data.set_mandate_id(payments_api::MandateIds { mandate_id: None, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 7c97516192e1..27d73d0df46d 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4071,6 +4071,7 @@ impl AttemptType { customer_acceptance: old_payment_attempt.customer_acceptance, organization_id: old_payment_attempt.organization_id, profile_id: old_payment_attempt.profile_id, + connector_mandate_detail: None, } } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 9ec2438ddb3a..ca32be35e550 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -614,7 +614,6 @@ impl GetTracker, api::PaymentsRequest> for Pa } else { (None, payment_method_info) }; - // The operation merges mandate data from both request and payment_attempt let setup_mandate = mandate_data.map(|mut sm| { sm.mandate_type = payment_attempt.mandate_details.clone().or(sm.mandate_type); diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 779b2d29c06f..fc1f71c2085d 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1206,8 +1206,10 @@ impl PaymentCreate { .map(Secret::new), organization_id: organization_id.clone(), profile_id, + connector_mandate_detail: None, }, additional_pm_data, + )) } diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 6ead05230202..3bb6647b527c 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1,11 +1,12 @@ use std::collections::HashMap; +use api_models::payments::{ConnectorMandateReferenceId, MandateReferenceId}; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use api_models::routing::RoutableConnectorChoice; use async_trait::async_trait; use common_enums::{AuthorizationStatus, SessionUpdateStatus}; use common_utils::{ - ext_traits::{AsyncExt, Encode}, + ext_traits::{AsyncExt, Encode, ValueExt}, types::{keymanager::KeyManagerState, ConnectorTransactionId, MinorUnit}, }; use error_stack::{report, ResultExt}; @@ -184,14 +185,11 @@ impl PostUpdateTracker, types::PaymentsAuthor let save_payment_call_future = Box::pin(tokenization::save_payment_method( state, connector_name.clone(), - merchant_connector_id.clone(), save_payment_data, customer_id.clone(), merchant_account, resp.request.payment_method_type, key_store, - Some(resp.request.amount), - Some(resp.request.currency), billing_name.clone(), payment_method_billing_address, business_profile, @@ -208,7 +206,7 @@ impl PostUpdateTracker, types::PaymentsAuthor resp.request.setup_future_usage, Some(enums::FutureUsage::OffSession) ); - + let storage_scheme = merchant_account.storage_scheme; if is_legacy_mandate { // Mandate is created on the application side and at the connector. let tokenization::SavePaymentMethodDataResponse { @@ -233,15 +231,46 @@ impl PostUpdateTracker, types::PaymentsAuthor // The mandate is created on connector's end. let tokenization::SavePaymentMethodDataResponse { payment_method_id, - mandate_reference_id, + connector_mandate_reference_id, .. } = save_payment_call_future.await?; - + payment_data.payment_method_info = if let Some(payment_method_id) = &payment_method_id { + match state + .store + .find_payment_method( + &(state.into()), + key_store, + payment_method_id, + storage_scheme, + ) + .await + { + Ok(payment_method) => Some(payment_method), + Err(error) => { + if error.current_context().is_db_not_found() { + logger::info!("Payment Method not found in db {:?}", error); + None + } else { + Err(error) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error retrieving payment method from db") + .map_err(|err| logger::error!(payment_method_retrieve=?err)) + .ok() + } + } + } + } else { + None + }; payment_data.payment_attempt.payment_method_id = payment_method_id; - + payment_data.payment_attempt.connector_mandate_detail = connector_mandate_reference_id + .clone() + .map(ForeignFrom::foreign_from); payment_data.set_mandate_id(api_models::payments::MandateIds { mandate_id: None, - mandate_reference_id, + mandate_reference_id: connector_mandate_reference_id.map(|connector_mandate_id| { + MandateReferenceId::ConnectorMandateId(connector_mandate_id) + }), }); Ok(()) } else if should_avoid_saving { @@ -256,16 +285,10 @@ impl PostUpdateTracker, types::PaymentsAuthor let key_store = key_store.clone(); let state = state.clone(); let customer_id = payment_data.payment_intent.customer_id.clone(); - - let merchant_connector_id = payment_data.payment_attempt.merchant_connector_id.clone(); let payment_attempt = payment_data.payment_attempt.clone(); let business_profile = business_profile.clone(); - - let amount = resp.request.amount; - let currency = resp.request.currency; let payment_method_type = resp.request.payment_method_type; - let storage_scheme = merchant_account.clone().storage_scheme; let payment_method_billing_address = payment_method_billing_address.cloned(); logger::info!("Call to save_payment_method in locker"); @@ -276,14 +299,11 @@ impl PostUpdateTracker, types::PaymentsAuthor let result = Box::pin(tokenization::save_payment_method( &state, connector_name, - merchant_connector_id, save_payment_data, customer_id, &merchant_account, payment_method_type, &key_store, - Some(amount), - Some(currency), billing_name, payment_method_billing_address.as_ref(), &business_profile, @@ -554,6 +574,33 @@ impl PostUpdateTracker, types::PaymentsSyncData> for where F: 'b + Clone + Send + Sync, { + let (connector_mandate_id, mandate_metadata) = resp + .response + .clone() + .ok() + .and_then(|resp| { + if let types::PaymentsResponseData::TransactionResponse { + mandate_reference, .. + } = resp + { + mandate_reference.map(|mandate_ref| { + ( + mandate_ref.connector_mandate_id.clone(), + mandate_ref.mandate_metadata.clone(), + ) + }) + } else { + None + } + }) + .unwrap_or((None, None)); + + update_connector_mandate_details_for_the_flow( + connector_mandate_id, + mandate_metadata, + payment_data, + )?; + update_payment_method_status_and_ntid( state, key_store, @@ -1038,19 +1085,16 @@ impl PostUpdateTracker, types::SetupMandateRequestDa let merchant_connector_id = payment_data.payment_attempt.merchant_connector_id.clone(); let tokenization::SavePaymentMethodDataResponse { payment_method_id, - mandate_reference_id, + connector_mandate_reference_id, .. } = Box::pin(tokenization::save_payment_method( state, connector_name, - merchant_connector_id.clone(), save_payment_data, customer_id.clone(), merchant_account, resp.request.payment_method_type, key_store, - resp.request.amount, - Some(resp.request.currency), billing_name, None, business_profile, @@ -1069,9 +1113,14 @@ impl PostUpdateTracker, types::SetupMandateRequestDa .await?; payment_data.payment_attempt.payment_method_id = payment_method_id; payment_data.payment_attempt.mandate_id = mandate_id; + payment_data.payment_attempt.connector_mandate_detail = connector_mandate_reference_id + .clone() + .map(ForeignFrom::foreign_from); payment_data.set_mandate_id(api_models::payments::MandateIds { mandate_id: None, - mandate_reference_id, + mandate_reference_id: connector_mandate_reference_id.map(|connector_mandate_id| { + MandateReferenceId::ConnectorMandateId(connector_mandate_id) + }), }); Ok(()) } @@ -1127,6 +1176,32 @@ impl PostUpdateTracker, types::CompleteAuthorizeData where F: 'b + Clone + Send + Sync, { + let (connector_mandate_id, mandate_metadata) = resp + .response + .clone() + .ok() + .and_then(|resp| { + if let types::PaymentsResponseData::TransactionResponse { + mandate_reference, .. + } = resp + { + mandate_reference.map(|mandate_ref| { + ( + mandate_ref.connector_mandate_id.clone(), + mandate_ref.mandate_metadata.clone(), + ) + }) + } else { + None + } + }) + .unwrap_or((None, None)); + update_connector_mandate_details_for_the_flow( + connector_mandate_id, + mandate_metadata, + payment_data, + )?; + update_payment_method_status_and_ntid( state, key_store, @@ -1487,6 +1562,78 @@ async fn payment_response_update_tracker( .payment_intent .fingerprint_id .clone_from(&payment_data.payment_attempt.fingerprint_id); + + if let Some(payment_method) = + payment_data.payment_method_info.clone() + { + // Parse value to check for mandates' existence + let mandate_details = payment_method + .connector_mandate_details + .clone() + .map(|val| { + val.parse_value::( + "PaymentsMandateReference", + ) + }) + .transpose() + .change_context( + errors::ApiErrorResponse::InternalServerError, + ) + .attach_printable( + "Failed to deserialize to Payment Mandate Reference ", + )?; + + if let Some(mca_id) = + payment_data.payment_attempt.merchant_connector_id.clone() + { + // check if the mandate has not already been set to active + if !mandate_details + .as_ref() + .map(|payment_mandate_reference| { + + payment_mandate_reference.0.get(&mca_id) + .map(|payment_mandate_reference_record| payment_mandate_reference_record.connector_mandate_status == Some(common_enums::ConnectorMandateStatus::Active)) + .unwrap_or(false) + }) + .unwrap_or(false) + { + let (connector_mandate_id, mandate_metadata) = payment_data.payment_attempt.connector_mandate_detail.clone() + .map(|cmr| (cmr.connector_mandate_id, cmr.mandate_metadata)) + .unwrap_or((None, None)); + + // Update the connector mandate details with the payment attempt connector mandate id + let connector_mandate_details = + tokenization::update_connector_mandate_details( + mandate_details, + payment_data.payment_attempt.payment_method_type, + Some( + payment_data + .payment_attempt + .net_amount + .get_total_amount() + .get_amount_as_i64(), + ), + payment_data.payment_attempt.currency, + payment_data.payment_attempt.merchant_connector_id.clone(), + connector_mandate_id, + mandate_metadata, + )?; + // Update the payment method table with the active mandate record + payment_methods::cards::update_payment_method_connector_mandate_details( + state, + key_store, + &*state.store, + payment_method, + connector_mandate_details, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update payment method in db")?; + } + } + } + metrics::SUCCESSFUL_PAYMENT.add(&metrics::CONTEXT, 1, &[]); } @@ -1561,6 +1708,10 @@ async fn payment_response_update_tracker( encoded_data, payment_method_data: additional_payment_method_data, charge_id, + connector_mandate_detail: payment_data + .payment_attempt + .connector_mandate_detail + .clone(), }), ), }; @@ -1760,59 +1911,6 @@ async fn payment_response_update_tracker( .in_current_span(), ); - // When connector requires redirection for mandate creation it can update the connector mandate_id in payment_methods during Psync and CompleteAuthorize - - let flow_name = core_utils::get_flow_name::()?; - if flow_name == "PSync" || flow_name == "CompleteAuthorize" { - let (connector_mandate_id, mandate_metadata) = match router_data.response.clone() { - Ok(resp) => match resp { - types::PaymentsResponseData::TransactionResponse { - ref mandate_reference, - .. - } => { - if let Some(mandate_ref) = mandate_reference { - ( - mandate_ref.connector_mandate_id.clone(), - mandate_ref.mandate_metadata.clone(), - ) - } else { - (None, None) - } - } - _ => (None, None), - }, - Err(_) => (None, None), - }; - if let Some(payment_method) = payment_data.payment_method_info.clone() { - let connector_mandate_details = - tokenization::update_connector_mandate_details_in_payment_method( - payment_method.clone(), - payment_method.payment_method_type, - Some( - payment_data - .payment_attempt - .net_amount - .get_total_amount() - .get_amount_as_i64(), - ), - payment_data.payment_attempt.currency, - payment_data.payment_attempt.merchant_connector_id.clone(), - connector_mandate_id, - mandate_metadata, - )?; - payment_methods::cards::update_payment_method_connector_mandate_details( - state, - key_store, - &*state.store, - payment_method.clone(), - connector_mandate_details, - storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to update payment method in db")? - } - } // When connector requires redirection for mandate creation it can update the connector mandate_id during Psync and CompleteAuthorize let m_db = state.clone().store; let m_router_data_merchant_id = router_data.merchant_id.clone(); @@ -2032,6 +2130,35 @@ async fn update_payment_method_status_and_ntid( Ok(()) } +fn update_connector_mandate_details_for_the_flow( + connector_mandate_id: Option, + mandate_metadata: Option, + payment_data: &mut PaymentData, +) -> RouterResult<()> { + let connector_mandate_reference_id = if connector_mandate_id.is_some() { + Some(ConnectorMandateReferenceId { + connector_mandate_id: connector_mandate_id.clone(), + payment_method_id: None, + update_history: None, + mandate_metadata: mandate_metadata.clone(), + }) + } else { + None + }; + + payment_data.payment_attempt.connector_mandate_detail = connector_mandate_reference_id + .clone() + .map(ForeignFrom::foreign_from); + + payment_data.set_mandate_id(api_models::payments::MandateIds { + mandate_id: None, + mandate_reference_id: connector_mandate_reference_id.map(|connector_mandate_id| { + MandateReferenceId::ConnectorMandateId(connector_mandate_id) + }), + }); + Ok(()) +} + fn response_to_capture_update( multiple_capture_data: &MultipleCaptureData, response_list: HashMap, diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 374c5680685f..e4b6d0e287c5 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -456,6 +456,7 @@ where unified_message: None, payment_method_data: additional_payment_method_data, charge_id, + connector_mandate_detail: None, }; #[cfg(feature = "v1")] @@ -642,6 +643,7 @@ pub fn make_new_payment_attempt( fingerprint_id: Default::default(), charge_id: Default::default(), customer_acceptance: Default::default(), + connector_mandate_detail: Default::default(), } } diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 213a9211c063..b4e5ade56980 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -5,8 +5,8 @@ use std::collections::HashMap; not(feature = "payment_methods_v2") ))] use api_models::payment_methods::PaymentMethodsData; -use api_models::payments::{ConnectorMandateReferenceId, MandateReferenceId}; -use common_enums::PaymentMethod; +use api_models::payments::ConnectorMandateReferenceId; +use common_enums::{ConnectorMandateStatus, PaymentMethod}; use common_utils::{ crypto::Encryptable, ext_traits::{AsyncExt, Encode, ValueExt}, @@ -61,7 +61,7 @@ impl From<&types::RouterData pub struct SavePaymentMethodDataResponse { pub payment_method_id: Option, pub payment_method_status: Option, - pub mandate_reference_id: Option, + pub connector_mandate_reference_id: Option, } #[cfg(all( any(feature = "v1", feature = "v2"), @@ -72,14 +72,11 @@ pub struct SavePaymentMethodDataResponse { pub async fn save_payment_method( state: &SessionState, connector_name: String, - merchant_connector_id: Option, save_payment_method_data: SavePaymentMethodData, customer_id: Option, merchant_account: &domain::MerchantAccount, payment_method_type: Option, key_store: &domain::MerchantKeyStore, - amount: Option, - currency: Option, billing_name: Option>, payment_method_billing_address: Option<&api::Address>, business_profile: &domain::Profile, @@ -174,32 +171,6 @@ where } _ => (None, None), }; - let check_for_mit_mandates = save_payment_method_data - .request - .get_setup_mandate_details() - .is_none() - && save_payment_method_data - .request - .get_setup_future_usage() - .map(|future_usage| future_usage == storage_enums::FutureUsage::OffSession) - .unwrap_or(false); - // insert in PaymentMethods if its a off-session mit payment - let connector_mandate_details = if check_for_mit_mandates { - add_connector_mandate_details_in_payment_method( - payment_method_type, - amount, - currency, - merchant_connector_id.clone(), - connector_mandate_id.clone(), - mandate_metadata.clone(), - ) - } else { - None - } - .map(|connector_mandate_data| connector_mandate_data.encode_to_value()) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to serialize customer acceptance to value")?; let pm_id = if customer_acceptance.is_some() { let payment_method_create_request = @@ -384,24 +355,6 @@ where .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to add payment method in db")?; - if check_for_mit_mandates { - let connector_mandate_details = - update_connector_mandate_details_in_payment_method( - pm.clone(), - payment_method_type, - amount, - currency, - merchant_connector_id.clone(), - connector_mandate_id.clone(), - mandate_metadata.clone(), - )?; - - payment_methods::cards::update_payment_method_connector_mandate_details(state, - key_store,db, pm, connector_mandate_details, merchant_account.storage_scheme).await.change_context( - errors::ApiErrorResponse::InternalServerError, - ) - .attach_printable("Failed to update payment method in db")?; - } } Err(err) => { if err.current_context().is_db_not_found() { @@ -418,7 +371,7 @@ where customer_acceptance, pm_data_encrypted.map(Into::into), key_store, - connector_mandate_details, + None, pm_status, network_transaction_id, merchant_account.storage_scheme, @@ -489,28 +442,7 @@ where resp.payment_method_id = payment_method_id; let existing_pm = match payment_method { - Ok(pm) => { - // update if its a off-session mit payment - if check_for_mit_mandates { - let connector_mandate_details = - update_connector_mandate_details_in_payment_method( - pm.clone(), - payment_method_type, - amount, - currency, - merchant_connector_id.clone(), - connector_mandate_id.clone(), - mandate_metadata.clone(), - )?; - - payment_methods::cards::update_payment_method_connector_mandate_details( state, - key_store,db, pm.clone(), connector_mandate_details, merchant_account.storage_scheme).await.change_context( - errors::ApiErrorResponse::InternalServerError, - ) - .attach_printable("Failed to update payment method in db")?; - } - Ok(pm) - } + Ok(pm) => Ok(pm), Err(err) => { if err.current_context().is_db_not_found() { payment_methods::cards::create_payment_method( @@ -524,7 +456,7 @@ where customer_acceptance, pm_data_encrypted.map(Into::into), key_store, - connector_mandate_details, + None, pm_status, network_transaction_id, merchant_account.storage_scheme, @@ -733,7 +665,7 @@ where customer_acceptance, pm_data_encrypted.map(Into::into), key_store, - connector_mandate_details, + None, pm_status, network_transaction_id, merchant_account.storage_scheme, @@ -755,35 +687,28 @@ where } else { None }; - let cmid_config = db - .find_config_by_key_unwrap_or( - format!("{}_should_show_connector_mandate_id_in_payments_response", merchant_account.get_id().get_string_repr().to_owned()).as_str(), - Some("false".to_string()), - ) - .await.map_err(|err| services::logger::error!(message="Failed to fetch the config", connector_mandate_details_population=?err)).ok(); - - let mandate_reference_id = match cmid_config { - Some(config) if config.config == "true" => Some( - MandateReferenceId::ConnectorMandateId(ConnectorMandateReferenceId { - connector_mandate_id: connector_mandate_id.clone(), - payment_method_id: pm_id.clone(), - update_history: None, - mandate_metadata: mandate_metadata.clone(), - }), - ), - _ => None, + // check if there needs to be a config if yes then remove it to a different place + let connector_mandate_reference_id = if connector_mandate_id.is_some() { + Some(ConnectorMandateReferenceId { + connector_mandate_id: connector_mandate_id.clone(), + payment_method_id: None, + update_history: None, + mandate_metadata: mandate_metadata.clone(), + }) + } else { + None }; Ok(SavePaymentMethodDataResponse { payment_method_id: pm_id, payment_method_status: pm_status, - mandate_reference_id, + connector_mandate_reference_id, }) } Err(_) => Ok(SavePaymentMethodDataResponse { payment_method_id: None, payment_method_status: None, - mandate_reference_id: None, + connector_mandate_reference_id: None, }), } } @@ -795,14 +720,11 @@ where pub async fn save_payment_method( _state: &SessionState, _connector_name: String, - _merchant_connector_id: Option, _save_payment_method_data: SavePaymentMethodData, _customer_id: Option, _merchant_account: &domain::MerchantAccount, _payment_method_type: Option, _key_store: &domain::MerchantKeyStore, - _amount: Option, - _currency: Option, _billing_name: Option>, _payment_method_billing_address: Option<&api::Address>, _business_profile: &domain::Profile, @@ -1226,6 +1148,7 @@ pub fn add_connector_mandate_details_in_payment_method( original_payment_authorized_amount: authorized_amount, original_payment_authorized_currency: authorized_currency, mandate_metadata, + connector_mandate_status: Some(ConnectorMandateStatus::Active), }, ); Some(storage::PaymentsMandateReference(mandate_details)) @@ -1234,8 +1157,8 @@ pub fn add_connector_mandate_details_in_payment_method( } } -pub fn update_connector_mandate_details_in_payment_method( - payment_method: domain::PaymentMethod, +pub fn update_connector_mandate_details( + mandate_details: Option, payment_method_type: Option, authorized_amount: Option, authorized_currency: Option, @@ -1243,17 +1166,8 @@ pub fn update_connector_mandate_details_in_payment_method( connector_mandate_id: Option, mandate_metadata: Option, ) -> RouterResult> { - let mandate_reference = match payment_method.connector_mandate_details { - Some(_) => { - let mandate_details = payment_method - .connector_mandate_details - .map(|val| { - val.parse_value::("PaymentsMandateReference") - }) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to deserialize to Payment Mandate Reference ")?; - + let mandate_reference = match mandate_details { + Some(mut payment_mandate_reference) => { if let Some((mca_id, connector_mandate_id)) = merchant_connector_id.clone().zip(connector_mandate_id) { @@ -1263,20 +1177,21 @@ pub fn update_connector_mandate_details_in_payment_method( original_payment_authorized_amount: authorized_amount, original_payment_authorized_currency: authorized_currency, mandate_metadata: mandate_metadata.clone(), + connector_mandate_status: Some(ConnectorMandateStatus::Active), }; - mandate_details.map(|mut payment_mandate_reference| { - payment_mandate_reference - .entry(mca_id) - .and_modify(|pm| *pm = updated_record) - .or_insert(storage::PaymentsMandateReferenceRecord { - connector_mandate_id, - payment_method_type, - original_payment_authorized_amount: authorized_amount, - original_payment_authorized_currency: authorized_currency, - mandate_metadata: mandate_metadata.clone(), - }); - payment_mandate_reference - }) + + payment_mandate_reference + .entry(mca_id) + .and_modify(|pm| *pm = updated_record) + .or_insert(storage::PaymentsMandateReferenceRecord { + connector_mandate_id, + payment_method_type, + original_payment_authorized_amount: authorized_amount, + original_payment_authorized_currency: authorized_currency, + mandate_metadata: mandate_metadata.clone(), + connector_mandate_status: Some(ConnectorMandateStatus::Active), + }); + Some(payment_mandate_reference) } else { None } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 36586fdb155b..0d9d684cae82 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1,8 +1,8 @@ use std::{fmt::Debug, marker::PhantomData, str::FromStr}; use api_models::payments::{ - Address, CustomerDetails, CustomerDetailsResponse, FrmMessage, PaymentChargeRequest, - PaymentChargeResponse, RequestSurchargeDetails, + Address, ConnectorMandateReferenceId, CustomerDetails, CustomerDetailsResponse, FrmMessage, + PaymentChargeRequest, PaymentChargeResponse, RequestSurchargeDetails, }; use common_enums::{Currency, RequestIncrementalAuthorization}; use common_utils::{ @@ -11,7 +11,10 @@ use common_utils::{ pii::Email, types::{self as common_utils_type, AmountConvertor, MinorUnit, StringMajorUnitForConnector}, }; -use diesel_models::ephemeral_key; +use diesel_models::{ + ephemeral_key, + payment_attempt::ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, +}; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{payments::payment_intent::CustomerData, router_request_types}; use masking::{ExposeInterface, Maskable, PeekInterface, Secret}; @@ -2849,3 +2852,23 @@ impl ForeignFrom } } } + +impl ForeignFrom for ConnectorMandateReferenceId { + fn foreign_from(value: DieselConnectorMandateReferenceId) -> Self { + Self { + connector_mandate_id: value.connector_mandate_id, + payment_method_id: value.payment_method_id, + update_history: None, + mandate_metadata: value.mandate_metadata, + } + } +} +impl ForeignFrom for DieselConnectorMandateReferenceId { + fn foreign_from(value: ConnectorMandateReferenceId) -> Self { + Self { + connector_mandate_id: value.connector_mandate_id, + payment_method_id: value.payment_method_id, + mandate_metadata: value.mandate_metadata, + } + } +} diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 3942c395665f..19670fc8ccb8 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -259,13 +259,13 @@ async fn incoming_webhooks_core( let merchant_connector_account = match merchant_connector_account { Some(merchant_connector_account) => merchant_connector_account, None => { - helper_utils::get_mca_from_object_reference_id( + Box::pin(helper_utils::get_mca_from_object_reference_id( &state, object_ref_id.clone(), &merchant_account, &connector_name, &key_store, - ) + )) .await? } }; diff --git a/crates/router/src/db/dashboard_metadata.rs b/crates/router/src/db/dashboard_metadata.rs index f8aee34c49c0..742c03dde4a0 100644 --- a/crates/router/src/db/dashboard_metadata.rs +++ b/crates/router/src/db/dashboard_metadata.rs @@ -99,7 +99,7 @@ impl DashboardMetadataInterface for Store { org_id: &id_type::OrganizationId, data_keys: Vec, ) -> CustomResult, errors::StorageError> { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::DashboardMetadata::find_user_scoped_dashboard_metadata( &conn, user_id.to_owned(), @@ -118,7 +118,7 @@ impl DashboardMetadataInterface for Store { org_id: &id_type::OrganizationId, data_keys: Vec, ) -> CustomResult, errors::StorageError> { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::DashboardMetadata::find_merchant_scoped_dashboard_metadata( &conn, merchant_id.to_owned(), diff --git a/crates/router/src/db/role.rs b/crates/router/src/db/role.rs index 20a4922d165b..ce009d38a9ec 100644 --- a/crates/router/src/db/role.rs +++ b/crates/router/src/db/role.rs @@ -80,7 +80,7 @@ impl RoleInterface for Store { &self, role_id: &str, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::Role::find_by_role_id(&conn, role_id) .await .map_err(|error| report!(errors::StorageError::from(error))) @@ -93,7 +93,7 @@ impl RoleInterface for Store { merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::Role::find_by_role_id_in_merchant_scope(&conn, role_id, merchant_id, org_id) .await .map_err(|error| report!(errors::StorageError::from(error))) @@ -105,7 +105,7 @@ impl RoleInterface for Store { role_id: &str, org_id: &id_type::OrganizationId, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::Role::find_by_role_id_in_org_scope(&conn, role_id, org_id) .await .map_err(|error| report!(errors::StorageError::from(error))) @@ -140,7 +140,7 @@ impl RoleInterface for Store { merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, ) -> CustomResult, errors::StorageError> { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::Role::list_roles(&conn, merchant_id, org_id) .await .map_err(|error| report!(errors::StorageError::from(error))) @@ -154,7 +154,7 @@ impl RoleInterface for Store { entity_type: Option, limit: Option, ) -> CustomResult, errors::StorageError> { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::Role::generic_roles_list_for_org( &conn, org_id.to_owned(), diff --git a/crates/router/src/db/user.rs b/crates/router/src/db/user.rs index c9244eb12b42..3cf68551b52f 100644 --- a/crates/router/src/db/user.rs +++ b/crates/router/src/db/user.rs @@ -71,7 +71,7 @@ impl UserInterface for Store { &self, user_email: &pii::Email, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::User::find_by_user_email(&conn, user_email) .await .map_err(|error| report!(errors::StorageError::from(error))) @@ -82,7 +82,7 @@ impl UserInterface for Store { &self, user_id: &str, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::User::find_by_user_id(&conn, user_id) .await .map_err(|error| report!(errors::StorageError::from(error))) @@ -127,7 +127,7 @@ impl UserInterface for Store { &self, user_ids: Vec, ) -> CustomResult, errors::StorageError> { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::User::find_users_by_user_ids(&conn, user_ids) .await .map_err(|error| report!(errors::StorageError::from(error))) diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs index 1fa70afa73f7..d32c6b254eb3 100644 --- a/crates/router/src/db/user_role.rs +++ b/crates/router/src/db/user_role.rs @@ -103,7 +103,7 @@ impl UserRoleInterface for Store { profile_id: Option<&id_type::ProfileId>, version: enums::UserRoleVersion, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_connection_read(self).await?; storage::UserRole::find_by_user_id_org_id_merchant_id_profile_id( &conn, user_id.to_owned(), diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 37387a01da3c..5300e86afdb9 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -434,7 +434,9 @@ impl ConnectorData { Ok(ConnectorEnum::Old(Box::new(connector::Itaubank::new()))) } enums::Connector::Klarna => Ok(ConnectorEnum::Old(Box::new(&connector::Klarna))), - enums::Connector::Mollie => Ok(ConnectorEnum::Old(Box::new(&connector::Mollie))), + enums::Connector::Mollie => { + Ok(ConnectorEnum::Old(Box::new(connector::Mollie::new()))) + } enums::Connector::Nexixpay => { Ok(ConnectorEnum::Old(Box::new(connector::Nexixpay::new()))) } diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 40335d5f2ab3..502e03b7293f 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -216,6 +216,7 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), + connector_mandate_detail: Default::default(), }; let store = state @@ -299,6 +300,7 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), + connector_mandate_detail: Default::default(), }; let store = state .stores @@ -395,6 +397,7 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), + connector_mandate_detail: Default::default(), }; let store = state .stores diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs index bb1b801edb6f..20d0ee74b011 100644 --- a/crates/router/src/types/storage/payment_method.rs +++ b/crates/router/src/types/storage/payment_method.rs @@ -125,6 +125,7 @@ pub struct PaymentsMandateReferenceRecord { pub original_payment_authorized_amount: Option, pub original_payment_authorized_currency: Option, pub mandate_metadata: Option, + pub connector_mandate_status: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 122989a82cfd..7faafb14ec18 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -339,6 +339,7 @@ pub async fn generate_sample_data( shipping_cost: None, order_tax_amount: None, connector_transaction_data, + connector_mandate_detail: None, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { diff --git a/crates/router/tests/connectors/mollie.rs b/crates/router/tests/connectors/mollie.rs index 7017363cad0f..d59bc78226dd 100644 --- a/crates/router/tests/connectors/mollie.rs +++ b/crates/router/tests/connectors/mollie.rs @@ -13,7 +13,7 @@ impl utils::Connector for MollieTest { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Mollie; utils::construct_connector_data_old( - Box::new(&Mollie), + Box::new(Mollie::new()), types::Connector::Mollie, types::api::GetToken::Connector, None, diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 59fb1155480f..121397cdfb9f 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -180,6 +180,7 @@ impl PaymentAttemptInterface for MockDb { customer_acceptance: payment_attempt.customer_acceptance, organization_id: payment_attempt.organization_id, profile_id: payment_attempt.profile_id, + connector_mandate_detail: payment_attempt.connector_mandate_detail, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 024ab9fd814b..5087e28b8542 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -531,6 +531,7 @@ impl PaymentAttemptInterface for KVRouterStore { customer_acceptance: payment_attempt.customer_acceptance.clone(), organization_id: payment_attempt.organization_id.clone(), profile_id: payment_attempt.profile_id.clone(), + connector_mandate_detail: payment_attempt.connector_mandate_detail.clone(), }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1453,6 +1454,7 @@ impl DataModelExt for PaymentAttempt { connector_transaction_data, shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), + connector_mandate_detail: self.connector_mandate_detail, } } @@ -1528,6 +1530,7 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, + connector_mandate_detail: storage_model.connector_mandate_detail, } } } @@ -1610,6 +1613,7 @@ impl DataModelExt for PaymentAttemptNew { profile_id: self.profile_id, shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), + connector_mandate_detail: self.connector_mandate_detail, } } @@ -1681,6 +1685,7 @@ impl DataModelExt for PaymentAttemptNew { customer_acceptance: storage_model.customer_acceptance, organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, + connector_mandate_detail: storage_model.connector_mandate_detail, } } } diff --git a/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/down.sql b/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/down.sql new file mode 100644 index 000000000000..f4e6f2e2e262 --- /dev/null +++ b/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE + payment_attempt DROP COLUMN connector_mandate_detail; \ No newline at end of file diff --git a/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/up.sql b/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/up.sql new file mode 100644 index 000000000000..bb592f623d43 --- /dev/null +++ b/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/up.sql @@ -0,0 +1,5 @@ +-- Your SQL goes here +ALTER TABLE + payment_attempt +ADD + COLUMN connector_mandate_detail JSONB DEFAULT NULL; \ No newline at end of file