diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index ecc76e9593..80547e7dba 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -593,10 +593,11 @@ impl x )), Ok(x) => Ok(format!( - "{}{}/{}", + "{}{}/{}{}", self.base_url(connectors), "v1/payment_intents", - x + x, + "?expand[0]=latest_charge" //updated payment_id(if present) reside inside latest_charge field )), x => x.change_context(errors::ConnectorError::MissingConnectorTransactionID), } @@ -1883,7 +1884,8 @@ impl services::ConnectorRedirectResponse for Stripe { .map_or( payments::CallConnectorAction::Trigger, |status| match status { - transformers::StripePaymentStatus::Failed => { + transformers::StripePaymentStatus::Failed + | transformers::StripePaymentStatus::Pending => { payments::CallConnectorAction::Trigger } _ => payments::CallConnectorAction::StatusUpdate { diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 845f3b4c35..bcf21c8ef3 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -70,19 +70,24 @@ pub enum Auth3ds { } #[derive(Debug, Eq, PartialEq, Serialize)] -#[serde(rename_all = "snake_case")] +#[serde( + rename_all = "snake_case", + tag = "mandate_data[customer_acceptance][type]" +)] pub enum StripeMandateType { - Online, + Online { + #[serde(rename = "mandate_data[customer_acceptance][online][ip_address]")] + ip_address: Secret, + #[serde(rename = "mandate_data[customer_acceptance][online][user_agent]")] + user_agent: String, + }, + Offline, } #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripeMandateRequest { - #[serde(rename = "mandate_data[customer_acceptance][type]")] - pub mandate_type: StripeMandateType, - #[serde(rename = "mandate_data[customer_acceptance][online][ip_address]")] - pub ip_address: Secret, - #[serde(rename = "mandate_data[customer_acceptance][online][user_agent]")] - pub user_agent: String, + #[serde(flatten)] + mandate_type: StripeMandateType, } #[derive(Debug, Eq, PartialEq, Serialize)] @@ -115,6 +120,8 @@ pub struct PaymentIntentRequest { pub payment_method_options: Option, // For mandate txns using network_txns_id, needs to be validated pub setup_future_usage: Option, pub off_session: Option, + #[serde(rename = "payment_method_types[0]")] + pub payment_method_types: Option, } #[derive(Debug, Eq, PartialEq, Serialize)] @@ -137,8 +144,6 @@ pub struct SetupIntentRequest { #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripeCardData { - #[serde(rename = "payment_method_types[]")] - pub payment_method_types: StripePaymentMethodType, #[serde(rename = "payment_method_data[type]")] pub payment_method_data_type: StripePaymentMethodType, #[serde(rename = "payment_method_data[card][number]")] @@ -154,8 +159,6 @@ pub struct StripeCardData { } #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripePayLaterData { - #[serde(rename = "payment_method_types[]")] - pub payment_method_types: StripePaymentMethodType, #[serde(rename = "payment_method_data[type]")] pub payment_method_data_type: StripePaymentMethodType, } @@ -277,8 +280,6 @@ fn get_bank_name( #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripeBankRedirectData { - #[serde(rename = "payment_method_types[]")] - pub payment_method_types: StripePaymentMethodType, #[serde(rename = "payment_method_data[type]")] pub payment_method_data_type: StripePaymentMethodType, // Required only for eps and ideal @@ -403,8 +404,6 @@ pub enum BankDebitData { #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripeBankDebitData { - #[serde(rename = "payment_method_types[]")] - pub payment_method_types: StripePaymentMethodType, #[serde(flatten)] pub bank_specific_data: BankDebitData, } @@ -459,16 +458,12 @@ pub struct ApplepayPayment { #[derive(Debug, Eq, PartialEq, Serialize)] pub struct AlipayPayment { - #[serde(rename = "payment_method_types[]")] - pub payment_method_types: StripePaymentMethodType, #[serde(rename = "payment_method_data[type]")] pub payment_method_data_type: StripePaymentMethodType, } #[derive(Debug, Eq, PartialEq, Serialize)] pub struct WechatpayPayment { - #[serde(rename = "payment_method_types[]")] - pub payment_method_types: StripePaymentMethodType, #[serde(rename = "payment_method_data[type]")] pub payment_method_data_type: StripePaymentMethodType, #[serde(rename = "payment_method_options[wechat_pay][client]")] @@ -520,6 +515,36 @@ pub enum StripePaymentMethodType { Multibanco, } +impl TryFrom for StripePaymentMethodType { + type Error = error_stack::Report; + fn try_from(value: enums::PaymentMethodType) -> Result { + match value { + enums::PaymentMethodType::Credit => Ok(Self::Card), + enums::PaymentMethodType::Debit => Ok(Self::Card), + enums::PaymentMethodType::Klarna => Ok(Self::Klarna), + enums::PaymentMethodType::Affirm => Ok(Self::Affirm), + enums::PaymentMethodType::AfterpayClearpay => Ok(Self::AfterpayClearpay), + enums::PaymentMethodType::Eps => Ok(Self::Eps), + enums::PaymentMethodType::Giropay => Ok(Self::Giropay), + enums::PaymentMethodType::Ideal => Ok(Self::Ideal), + enums::PaymentMethodType::Sofort => Ok(Self::Sofort), + enums::PaymentMethodType::ApplePay => Ok(Self::ApplePay), + enums::PaymentMethodType::Ach => Ok(Self::Ach), + enums::PaymentMethodType::Sepa => Ok(Self::Sepa), + enums::PaymentMethodType::Becs => Ok(Self::Becs), + enums::PaymentMethodType::Bacs => Ok(Self::Bacs), + enums::PaymentMethodType::BancontactCard => Ok(Self::Bancontact), + enums::PaymentMethodType::WeChatPay => Ok(Self::Wechatpay), + enums::PaymentMethodType::AliPay => Ok(Self::Alipay), + enums::PaymentMethodType::Przelewy24 => Ok(Self::Przelewy24), + _ => Err(errors::ConnectorError::NotImplemented( + "this payment method for stripe".to_string(), + ) + .into()), + } + } +} + #[derive(Debug, Eq, PartialEq, Serialize, Clone)] #[serde(rename_all = "snake_case")] pub enum BankTransferType { @@ -873,6 +898,7 @@ impl TryFrom<&payments::BankRedirectData> for StripeBillingAddress { billing_details, .. } => Ok(Self { name: billing_details.billing_name.clone(), + email: billing_details.email.clone(), ..Self::default() }), payments::BankRedirectData::Przelewy24 { @@ -883,14 +909,39 @@ impl TryFrom<&payments::BankRedirectData> for StripeBillingAddress { }), payments::BankRedirectData::BancontactCard { billing_details, .. + } => { + let billing_details = billing_details.as_ref().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "billing_details", + }, + )?; + Ok(Self { + name: Some( + billing_details + .billing_name + .as_ref() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "billing_details.billing_name", + })? + .to_owned(), + ), + email: Some( + billing_details + .email + .as_ref() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "billing_details.email", + })? + .to_owned(), + ), + ..Self::default() + }) + } + payments::BankRedirectData::Sofort { + billing_details, .. } => Ok(Self { - name: billing_details - .as_ref() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "bancontact_card.billing_name", - })? - .billing_name - .clone(), + name: billing_details.billing_name.clone(), + email: billing_details.email.clone(), ..Self::default() }), _ => Ok(Self::default()), @@ -996,7 +1047,6 @@ fn create_stripe_payment_method( }; Ok(( StripePaymentMethodData::Card(StripeCardData { - payment_method_types: StripePaymentMethodType::Card, payment_method_data_type: StripePaymentMethodType::Card, payment_method_data_card_number: card_details.card_number.clone(), payment_method_data_card_exp_month: card_details.card_exp_month.clone(), @@ -1023,7 +1073,6 @@ fn create_stripe_payment_method( Ok(( StripePaymentMethodData::PayLater(StripePayLaterData { - payment_method_types: stripe_pm_type, payment_method_data_type: stripe_pm_type, }), stripe_pm_type, @@ -1038,7 +1087,6 @@ fn create_stripe_payment_method( Ok(( StripePaymentMethodData::BankRedirect(StripeBankRedirectData { - payment_method_types: pm_type, payment_method_data_type: pm_type, bank_name, bank_specific_data, @@ -1063,7 +1111,6 @@ fn create_stripe_payment_method( payments::WalletData::WeChatPay(_) => Ok(( StripePaymentMethodData::Wallet(StripeWallet::WechatpayPayment(WechatpayPayment { client: WechatClient::Web, - payment_method_types: StripePaymentMethodType::Wechatpay, payment_method_data_type: StripePaymentMethodType::Wechatpay, })), StripePaymentMethodType::Wechatpay, @@ -1071,7 +1118,6 @@ fn create_stripe_payment_method( )), payments::WalletData::AliPayRedirect(_) => Ok(( StripePaymentMethodData::Wallet(StripeWallet::AlipayPayment(AlipayPayment { - payment_method_types: StripePaymentMethodType::Alipay, payment_method_data_type: StripePaymentMethodType::Alipay, })), StripePaymentMethodType::Alipay, @@ -1091,7 +1137,6 @@ fn create_stripe_payment_method( let (pm_type, bank_debit_data, billing_address) = get_bank_debit_data(bank_debit_data); let pm_data = StripePaymentMethodData::BankDebit(StripeBankDebitData { - payment_method_types: pm_type, bank_specific_data: bank_debit_data, }); @@ -1232,7 +1277,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { }; let mut payment_method_options = None; - let (mut payment_data, payment_method, mandate, billing_address) = { + let (mut payment_data, payment_method, mandate, billing_address, payment_method_types) = { match item .request .mandate_id @@ -1246,6 +1291,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { connector_mandate_ids.payment_method_id, connector_mandate_ids.connector_mandate_id, StripeBillingAddress::default(), + None, ), Some(api_models::payments::MandateReferenceId::NetworkMandateId( network_transaction_id, @@ -1257,7 +1303,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { network_transaction_id: Secret::new(network_transaction_id), }), }); - (None, None, None, StripeBillingAddress::default()) + (None, None, None, StripeBillingAddress::default(), None) } _ => { let (payment_method_data, payment_method_type, billing_address) = @@ -1273,11 +1319,26 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { &payment_method_type, )?; - (Some(payment_method_data), None, None, billing_address) + ( + Some(payment_method_data), + None, + None, + billing_address, + Some(payment_method_type), + ) } } }; + //if payment_method is Some(), That means, it is a recurring mandate payment. + //payment_method_types will not be not be available in router_data.request. But stripe requires that field. + //here we will get that field from router_data.recurring_mandate_payment_data + let payment_method_types = if payment_method.is_some() { + //if recurring payment get payment_method_type + get_payment_method_type_for_saved_payment_method_payment(item).map(Some)? + } else { + payment_method_types + }; payment_data = match item.request.payment_method_data { payments::PaymentMethodData::Wallet(payments::WalletData::ApplePay(_)) => Some( StripePaymentMethodData::Wallet(StripeWallet::ApplepayPayment(ApplepayPayment { @@ -1300,25 +1361,62 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { .and_then(|mandate_details| { mandate_details .customer_acceptance - .as_ref()? - .online .as_ref() - .map(|online_details| { - Ok::<_, error_stack::Report>(StripeMandateRequest { - mandate_type: StripeMandateType::Online, - ip_address: online_details - .ip_address - .clone() - .get_required_value("ip_address") - .change_context(errors::ConnectorError::MissingRequiredField { - field_name: "ip_address", - })?, - user_agent: online_details.user_agent.to_owned(), - }) + .map(|customer_acceptance| { + Ok::<_, error_stack::Report>( + match customer_acceptance.acceptance_type { + payments::AcceptanceType::Online => { + let online_mandate = customer_acceptance + .online + .clone() + .get_required_value("online") + .change_context( + errors::ConnectorError::MissingRequiredField { + field_name: "online", + }, + )?; + StripeMandateRequest { + mandate_type: StripeMandateType::Online { + ip_address: online_mandate + .ip_address + .get_required_value("ip_address") + .change_context( + errors::ConnectorError::MissingRequiredField { + field_name: "ip_address", + }, + )?, + user_agent: online_mandate.user_agent, + }, + } + } + payments::AcceptanceType::Offline => StripeMandateRequest { + mandate_type: StripeMandateType::Offline, + }, + }, + ) }) }) - .transpose()?; - + .transpose()? + .or_else(|| { + //stripe requires us to send mandate_data while making recurring payment through saved bank debit + if payment_method.is_some() { + //check if payment is done through saved payment method + match &payment_method_types { + //check if payment method is bank debit + Some( + StripePaymentMethodType::Ach + | StripePaymentMethodType::Sepa + | StripePaymentMethodType::Becs + | StripePaymentMethodType::Bacs, + ) => Some(StripeMandateRequest { + mandate_type: StripeMandateType::Offline, + }), + _ => None, + } + } else { + None + } + }); Ok(Self { amount: item.request.amount, //hopefully we don't loose some cents here currency: item.request.currency.to_string(), //we need to copy the value and not transfer ownership @@ -1345,10 +1443,38 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { setup_mandate_details, off_session: item.request.off_session, setup_future_usage: item.request.setup_future_usage, + payment_method_types, }) } } +fn get_payment_method_type_for_saved_payment_method_payment( + item: &types::PaymentsAuthorizeRouterData, +) -> Result> { + let stripe_payment_method_type = match item.recurring_mandate_payment_data.clone() { + Some(recurring_payment_method_data) => { + match recurring_payment_method_data.payment_method_type { + Some(payment_method_type) => StripePaymentMethodType::try_from(payment_method_type), + None => Err(errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_type", + } + .into()), + } + } + None => Err(errors::ConnectorError::MissingRequiredField { + field_name: "recurring_mandate_payment_data", + } + .into()), + }?; + match stripe_payment_method_type { + //Stripe converts Ideal, Bancontact & Sofort Bank redirect methods to Sepa direct debit and attaches to the customer for future usage + StripePaymentMethodType::Ideal + | StripePaymentMethodType::Bancontact + | StripePaymentMethodType::Sofort => Ok(StripePaymentMethodType::Sepa), + _ => Ok(stripe_payment_method_type), + } +} + impl TryFrom<&types::VerifyRouterData> for SetupIntentRequest { type Error = error_stack::Report; fn try_from(item: &types::VerifyRouterData) -> Result { @@ -1552,6 +1678,19 @@ pub struct PaymentIntentSyncResponse { #[serde(flatten)] payment_intent_fields: PaymentIntentResponse, pub last_payment_error: Option, + pub latest_charge: Option, +} + +#[derive(Deserialize, Clone, Debug)] +pub struct StripeCharge { + pub id: String, + pub payment_method_details: Option, +} + +#[derive(Deserialize, Clone, Debug)] +pub struct StripeBankRedirectDetails { + #[serde(rename = "generated_sepa_debit")] + attached_payment_method: Option, } impl Deref for PaymentIntentSyncResponse { @@ -1562,6 +1701,45 @@ impl Deref for PaymentIntentSyncResponse { } } +#[derive(Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case", tag = "type")] +pub enum StripePaymentMethodDetailsResponse { + //only ideal, sofort and bancontact is supported by stripe for recurring payment in bank redirect + Ideal { + ideal: StripeBankRedirectDetails, + }, + Sofort { + sofort: StripeBankRedirectDetails, + }, + Bancontact { + bancontact: StripeBankRedirectDetails, + }, + + //other payment method types supported by stripe. To avoid deserialization error. + Blik, + Eps, + Fpx, + Giropay, + #[serde(rename = "p24")] + Przelewy24, + Card, + Klarna, + Affirm, + AfterpayClearpay, + ApplePay, + #[serde(rename = "us_bank_account")] + Ach, + #[serde(rename = "sepa_debit")] + Sepa, + #[serde(rename = "au_becs_debit")] + Becs, + #[serde(rename = "bacs_debit")] + Bacs, + #[serde(rename = "wechat_pay")] + Wechatpay, + Alipay, +} + #[derive(Deserialize)] pub struct SetupIntentSyncResponse { #[serde(flatten)] @@ -1582,6 +1760,7 @@ impl From for PaymentIntentSyncResponse { Self { payment_intent_fields: value.setup_intent_fields.into(), last_payment_error: value.last_payment_error, + latest_charge: None, } } } @@ -1772,7 +1951,21 @@ impl let mandate_reference = item.response.payment_method.clone().map(|pm| { types::MandateReference::foreign_from(( item.response.payment_method_options.clone(), - pm, + match item.response.latest_charge.clone() { + Some(charge) => match charge.payment_method_details { + Some(StripePaymentMethodDetailsResponse::Bancontact { bancontact }) => { + bancontact.attached_payment_method.unwrap_or(pm) + } + Some(StripePaymentMethodDetailsResponse::Ideal { ideal }) => { + ideal.attached_payment_method.unwrap_or(pm) + } + Some(StripePaymentMethodDetailsResponse::Sofort { sofort }) => { + sofort.attached_payment_method.unwrap_or(pm) + } + _ => pm, + }, + None => pm, + }, )) }); let error_res = @@ -2583,7 +2776,6 @@ impl enums::AuthenticationType::NoThreeDs => Auth3ds::Automatic, }; StripeCardData { - payment_method_types: StripePaymentMethodType::Card, payment_method_data_type: StripePaymentMethodType::Card, payment_method_data_card_number: ccard.card_number.clone(), payment_method_data_card_exp_month: ccard.card_exp_month.clone(), @@ -2593,12 +2785,10 @@ impl } })), api::PaymentMethodData::PayLater(_) => Ok(Self::PayLater(StripePayLaterData { - payment_method_types: pm_type, payment_method_data_type: pm_type, })), api::PaymentMethodData::BankRedirect(_) => { Ok(Self::BankRedirect(StripeBankRedirectData { - payment_method_types: pm_type, payment_method_data_type: pm_type, bank_name: None, bank_specific_data: None, @@ -2620,14 +2810,12 @@ impl payments::WalletData::WeChatPayRedirect(_) => { let wallet_info = StripeWallet::WechatpayPayment(WechatpayPayment { client: WechatClient::Web, - payment_method_types: StripePaymentMethodType::Wechatpay, payment_method_data_type: StripePaymentMethodType::Wechatpay, }); Ok(Self::Wallet(wallet_info)) } payments::WalletData::AliPayRedirect(_) => { let wallet_info = StripeWallet::AlipayPayment(AlipayPayment { - payment_method_types: StripePaymentMethodType::Alipay, payment_method_data_type: StripePaymentMethodType::Alipay, }); Ok(Self::Wallet(wallet_info)) @@ -2636,10 +2824,9 @@ impl _ => Err(errors::ConnectorError::InvalidWallet.into()), }, api::PaymentMethodData::BankDebit(bank_debit_data) => { - let (pm_type, bank_data, _) = get_bank_debit_data(&bank_debit_data); + let (_pm_type, bank_data, _) = get_bank_debit_data(&bank_debit_data); Ok(Self::BankDebit(StripeBankDebitData { - payment_method_types: pm_type, bank_specific_data: bank_data, })) } diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index 3e09bf21a3..f5b004046f 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -1,3 +1,4 @@ +use api_models::payments; use common_utils::{ext_traits::Encode, pii}; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; @@ -123,6 +124,18 @@ pub async fn get_customer_mandates( } } +fn get_insensitive_payment_method_data_if_exists( + router_data: &types::RouterData, +) -> Option +where + FData: MandateBehaviour, +{ + match &router_data.request.get_payment_method_data() { + api_models::payments::PaymentMethodData::Card(_) => None, + _ => Some(router_data.request.get_payment_method_data()), + } +} + pub async fn mandate_procedure( state: &AppState, mut resp: types::RouterData, @@ -209,6 +222,7 @@ where pm_id.get_required_value("payment_method_id")?, mandate_ids, network_txn_id, + get_insensitive_payment_method_data_if_exists(&resp), )? { let connector = new_mandate_data.connector.clone(); logger::debug!("{:?}", new_mandate_data); diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 80958f641b..24c6ac5340 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1006,7 +1006,6 @@ where .to_domain()? .make_pm_data(state, &mut payment_data, validate_result.storage_scheme) .await?; - payment_data.payment_method_data = payment_method_data; TokenizationAction::SkipConnectorTokenization } @@ -1036,7 +1035,6 @@ where .to_domain()? .make_pm_data(state, &mut payment_data, validate_result.storage_scheme) .await?; - payment_data.payment_method_data = payment_method_data; (payment_data, TokenizationAction::SkipConnectorTokenization) } @@ -1091,10 +1089,16 @@ where pub creds_identifier: Option, pub pm_token: Option, pub connector_customer_id: Option, + pub recurring_mandate_payment_data: Option, pub ephemeral_key: Option, pub redirect_response: Option, } +#[derive(Debug, Default, Clone)] +pub struct RecurringMandatePaymentData { + pub payment_method_type: Option, //required for making recurring payment using saved payment method through stripe +} + #[derive(Debug, Default)] pub struct CustomerDetails { pub customer_id: Option, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 8fd6321049..9faebe9d1a 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -273,6 +273,7 @@ pub async fn get_token_pm_type_mandate_details( Option, Option, Option, + Option, Option, )> { match mandate_type { @@ -287,16 +288,23 @@ pub async fn get_token_pm_type_mandate_details( request.payment_method_type, Some(setup_mandate), None, + None, )) } Some(api::MandateTransactionType::RecurringMandateTransaction) => { - let (token_, payment_method_, payment_method_type_, mandate_connector) = - get_token_for_recurring_mandate(state, request, merchant_account).await?; + let ( + token_, + payment_method_, + recurring_mandate_payment_data, + payment_method_type_, + mandate_connector, + ) = get_token_for_recurring_mandate(state, request, merchant_account).await?; Ok(( token_, payment_method_, payment_method_type_.or(request.payment_method_type), None, + recurring_mandate_payment_data, mandate_connector, )) } @@ -306,6 +314,7 @@ pub async fn get_token_pm_type_mandate_details( request.payment_method_type, request.mandate_data.clone(), None, + None, )), } } @@ -317,6 +326,7 @@ pub async fn get_token_for_recurring_mandate( ) -> RouterResult<( Option, Option, + Option, Option, Option, )> { @@ -355,6 +365,7 @@ pub async fn get_token_for_recurring_mandate( .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; let token = Uuid::new_v4().to_string(); + let payment_method_type = payment_method.payment_method_type; if let diesel_models::enums::PaymentMethod::Card = payment_method.payment_method { let _ = cards::get_lookup_key_from_locker(state, &token, &payment_method).await?; if let Some(payment_method_from_request) = req.payment_method { @@ -372,6 +383,9 @@ pub async fn get_token_for_recurring_mandate( Ok(( Some(token), Some(payment_method.payment_method), + Some(payments::RecurringMandatePaymentData { + payment_method_type, + }), payment_method.payment_method_type, Some(mandate.connector), )) @@ -379,6 +393,9 @@ pub async fn get_token_for_recurring_mandate( Ok(( None, Some(payment_method.payment_method), + Some(payments::RecurringMandatePaymentData { + payment_method_type, + }), payment_method.payment_method_type, Some(mandate.connector), )) @@ -1612,6 +1629,7 @@ pub fn check_if_operation_confirm(operations: Op) -> bool { format!("{operations:?}") == "PaymentConfirm" } +#[allow(clippy::too_many_arguments)] pub fn generate_mandate( merchant_id: String, connector: String, @@ -1620,6 +1638,7 @@ pub fn generate_mandate( payment_method_id: String, connector_mandate_id: Option, network_txn_id: Option, + payment_method_data_option: Option, ) -> CustomResult, errors::ApiErrorResponse> { match (setup_mandate_details, customer) { (Some(data), Some(cus)) => { @@ -1646,7 +1665,12 @@ pub fn generate_mandate( .map(masking::Secret::new), ) .set_customer_user_agent(customer_acceptance.get_user_agent()) - .set_customer_accepted_at(Some(customer_acceptance.get_accepted_at())); + .set_customer_accepted_at(Some(customer_acceptance.get_accepted_at())) + .set_metadata(payment_method_data_option.map(|payment_method_data| { + pii::SecretSerdeValue::new( + serde_json::to_value(payment_method_data).unwrap_or_default(), + ) + })); Ok(Some( match data.mandate_type.get_required_value("mandate_type")? { @@ -1661,8 +1685,9 @@ pub fn generate_mandate( .set_mandate_amount(Some(data.amount)) .set_mandate_currency(Some(data.currency)) .set_start_date(data.start_date) - .set_end_date(data.end_date) - .set_metadata(data.metadata), + .set_end_date(data.end_date), + // .set_metadata(data.metadata), + // we are storing PaymentMethodData in metadata of mandate None => &mut new_mandate, } .set_mandate_type(storage_enums::MandateType::MultiUse) @@ -2160,6 +2185,7 @@ pub fn router_data_type_conversion( customer_id: router_data.customer_id, connector_customer: router_data.connector_customer, preprocessing_id: router_data.preprocessing_id, + recurring_mandate_payment_data: router_data.recurring_mandate_payment_data, connector_request_reference_id: router_data.connector_request_reference_id, test_mode: router_data.test_mode, } diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 8256de6982..fde900673b 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -159,6 +159,7 @@ impl GetTracker, api::PaymentsCancelRequest> creds_identifier, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, }, diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 4984dbd0f0..ba31d8c9b8 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -164,6 +164,7 @@ impl GetTracker, api::PaymentsCaptu creds_identifier, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, }, 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 6c16eb85de..94feb273c1 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -72,14 +72,20 @@ impl GetTracker, api::PaymentsRequest> for Co "confirm", )?; - let (token, payment_method, payment_method_type, setup_mandate, mandate_connector) = - helpers::get_token_pm_type_mandate_details( - state, - request, - mandate_type.clone(), - merchant_account, - ) - .await?; + let ( + token, + payment_method, + payment_method_type, + setup_mandate, + recurring_mandate_payment_data, + mandate_connector, + ) = helpers::get_token_pm_type_mandate_details( + state, + request, + mandate_type.clone(), + merchant_account, + ) + .await?; let browser_info = request .browser_info @@ -225,6 +231,7 @@ impl GetTracker, api::PaymentsRequest> for Co creds_identifier: None, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data, ephemeral_key: None, redirect_response, }, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 301fd23006..102aa35967 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -107,14 +107,20 @@ impl GetTracker, api::PaymentsRequest> for Pa .setup_future_usage .or(payment_intent.setup_future_usage); - let (token, payment_method, payment_method_type, setup_mandate, mandate_connector) = - helpers::get_token_pm_type_mandate_details( - state, - request, - mandate_type.clone(), - merchant_account, - ) - .await?; + let ( + token, + payment_method, + payment_method_type, + setup_mandate, + recurring_mandate_payment_data, + mandate_connector, + ) = helpers::get_token_pm_type_mandate_details( + state, + request, + mandate_type.clone(), + merchant_account, + ) + .await?; let browser_info = request .browser_info @@ -281,6 +287,7 @@ impl GetTracker, api::PaymentsRequest> for Pa creds_identifier, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data, ephemeral_key: None, redirect_response: None, }, diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index b621e59c9b..e456f58a0e 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -64,14 +64,20 @@ impl GetTracker, api::PaymentsRequest> for Pa .get_payment_intent_id() .change_context(errors::ApiErrorResponse::PaymentNotFound)?; - let (token, payment_method, payment_method_type, setup_mandate, mandate_connector) = - helpers::get_token_pm_type_mandate_details( - state, - request, - mandate_type, - merchant_account, - ) - .await?; + let ( + token, + payment_method, + payment_method_type, + setup_mandate, + recurring_mandate_payment_data, + mandate_connector, + ) = helpers::get_token_pm_type_mandate_details( + state, + request, + mandate_type, + merchant_account, + ) + .await?; let customer_details = helpers::get_customer_details_from_request(request); @@ -260,6 +266,7 @@ impl GetTracker, api::PaymentsRequest> for Pa creds_identifier, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data, ephemeral_key, redirect_response: None, }, 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 34f5b66c18..712e46acdb 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -185,6 +185,7 @@ impl GetTracker, api::VerifyRequest> for Paym creds_identifier, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, }, diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 4aa523e0d2..0110e84f98 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -177,6 +177,7 @@ impl GetTracker, api::PaymentsSessionRequest> creds_identifier, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, }, diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 282b89edc2..af2df5f1b1 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -149,6 +149,7 @@ impl GetTracker, api::PaymentsStartRequest> f creds_identifier: None, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, }, diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index bcb268eb4a..6a6ee01f70 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -321,6 +321,7 @@ async fn get_tracker_for_sync< creds_identifier, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, }, diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index daeaeb32d7..296487c9ac 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -79,14 +79,20 @@ impl GetTracker, api::PaymentsRequest> for Pa "update", )?; - let (token, payment_method, payment_method_type, setup_mandate, mandate_connector) = - helpers::get_token_pm_type_mandate_details( - state, - request, - mandate_type.clone(), - merchant_account, - ) - .await?; + let ( + token, + payment_method, + payment_method_type, + setup_mandate, + recurring_mandate_payment_data, + mandate_connector, + ) = helpers::get_token_pm_type_mandate_details( + state, + request, + mandate_type.clone(), + merchant_account, + ) + .await?; payment_intent = db .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) @@ -329,6 +335,7 @@ impl GetTracker, api::PaymentsRequest> for Pa creds_identifier, pm_token: None, connector_customer_id: None, + recurring_mandate_payment_data, ephemeral_key: None, redirect_response: None, }, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 4387fad219..b9d805a300 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -126,6 +126,7 @@ where reference_id: None, payment_method_token: payment_data.pm_token, connector_customer: payment_data.connector_customer_id, + recurring_mandate_payment_data: payment_data.recurring_mandate_payment_data, connector_request_reference_id: core_utils::get_connector_request_reference_id( &state.conf, &merchant_account.merchant_id, diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 30179bdcb3..3d81ed913d 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -113,6 +113,7 @@ pub async fn construct_refund_router_data<'a, F>( reference_id: None, payment_method_token: None, connector_customer: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: get_connector_request_reference_id( &state.conf, @@ -307,6 +308,7 @@ pub async fn construct_accept_dispute_router_data<'a>( payment_method_token: None, connector_customer: None, customer_id: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: get_connector_request_reference_id( &state.conf, @@ -375,6 +377,7 @@ pub async fn construct_submit_evidence_router_data<'a>( payment_method_token: None, connector_customer: None, customer_id: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: get_connector_request_reference_id( &state.conf, @@ -444,6 +447,7 @@ pub async fn construct_upload_file_router_data<'a>( payment_method_token: None, connector_customer: None, customer_id: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: get_connector_request_reference_id( &state.conf, @@ -515,6 +519,7 @@ pub async fn construct_defend_dispute_router_data<'a>( payment_method_token: None, customer_id: None, connector_customer: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: get_connector_request_reference_id( &state.conf, @@ -584,6 +589,7 @@ pub async fn construct_retrieve_file_router_data<'a>( session_token: None, reference_id: None, payment_method_token: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_DISPUTE_FLOW .to_string(), diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index d75e72b40e..23142a7011 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -20,7 +20,10 @@ use masking::Secret; use self::{api::payments, storage::enums as storage_enums}; pub use crate::core::payments::PaymentAddress; -use crate::{core::errors, services}; +use crate::{ + core::{errors, payments::RecurringMandatePaymentData}, + services, +}; pub type PaymentsAuthorizeRouterData = RouterData; @@ -194,6 +197,7 @@ pub struct RouterData { pub session_token: Option, pub reference_id: Option, pub payment_method_token: Option, + pub recurring_mandate_payment_data: Option, pub preprocessing_id: Option, /// Contains flow-specific data required to construct a request and send it to the connector. @@ -820,6 +824,7 @@ impl From<(&RouterData, T2)> payment_method_token: None, preprocessing_id: None, connector_customer: data.connector_customer.clone(), + recurring_mandate_payment_data: data.recurring_mandate_payment_data.clone(), connector_request_reference_id: data.connector_request_reference_id.clone(), test_mode: data.test_mode, } diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index ea081a39e4..d34dfe063d 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -79,6 +79,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { reference_id: None, payment_method_token: None, connector_customer: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: uuid::Uuid::new_v4().to_string(), test_mode: None, @@ -125,6 +126,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { reference_id: None, payment_method_token: None, connector_customer: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: uuid::Uuid::new_v4().to_string(), test_mode: None, diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 971c857c38..afcb5e6120 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -402,6 +402,7 @@ pub trait ConnectorActions: Connector { reference_id: None, payment_method_token: None, connector_customer: None, + recurring_mandate_payment_data: None, preprocessing_id: None, connector_request_reference_id: uuid::Uuid::new_v4().to_string(), test_mode: None,