diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 8cd2a0c329..de0cd91e52 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -302,14 +302,20 @@ pub enum MandateTxnType { #[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct MandateIds { pub mandate_id: String, - pub connector_mandate_id: Option, + pub mandate_reference_id: Option, +} + +#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] +pub enum MandateReferenceId { + ConnectorMandateId(String), // mandate_id send by connector + NetworkMandateId(String), // network_txns_id send by Issuer to connector, Used for PG agnostic mandate txns } impl MandateIds { pub fn new(mandate_id: String) -> Self { Self { mandate_id, - connector_mandate_id: None, + mandate_reference_id: None, } } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 4955f437aa..243b05e2c4 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -65,12 +65,33 @@ pub struct Settings { #[cfg(feature = "s3")] pub file_upload_config: FileUploadConfig, pub tokenization: TokenizationConfig, + pub connector_customer: ConnectorCustomer, } #[derive(Debug, Deserialize, Clone, Default)] #[serde(transparent)] pub struct TokenizationConfig(pub HashMap); +#[derive(Debug, Deserialize, Clone, Default)] +pub struct ConnectorCustomer { + #[serde(deserialize_with = "connector_deser")] + pub connector_list: HashSet, +} + +fn connector_deser<'a, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'a>, +{ + let value = ::deserialize(deserializer)?; + Ok(value + .trim() + .split(',') + .flat_map(api_models::enums::Connector::from_str) + .collect()) +} + #[derive(Debug, Deserialize, Clone, Default)] pub struct PaymentMethodTokenFilter { #[serde(deserialize_with = "pm_deser")] diff --git a/crates/router/src/connector/aci/transformers.rs b/crates/router/src/connector/aci/transformers.rs index 01637c3b30..7ac9e43031 100644 --- a/crates/router/src/connector/aci/transformers.rs +++ b/crates/router/src/connector/aci/transformers.rs @@ -337,6 +337,7 @@ impl redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 5c9c85a789..72ae3d0e5d 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -1,11 +1,13 @@ -use api_models::{enums::DisputeStage, webhooks::IncomingWebhookEvent}; +use api_models::{ + enums::DisputeStage, payments::MandateReferenceId, webhooks::IncomingWebhookEvent, +}; use masking::PeekInterface; use reqwest::Url; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use crate::{ - connector::utils::PaymentsAuthorizeRequestData, + connector::utils::{self, CardData, PaymentsAuthorizeRequestData, RouterData}, consts, core::errors, pii::{self, Email, Secret}, @@ -37,18 +39,24 @@ pub enum AdyenShopperInteraction { #[serde(rename_all = "camelCase")] pub enum AdyenRecurringModel { UnscheduledCardOnFile, + CardOnFile, } -#[derive(Default, Debug, Serialize, Deserialize)] +#[derive(Clone, Default, Debug, Serialize, Deserialize)] pub enum AuthType { #[default] PreAuth, } -#[derive(Default, Debug, Serialize, Deserialize)] +#[derive(Clone, Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdditionalData { - authorisation_type: AuthType, - manual_capture: bool, + authorisation_type: Option, + manual_capture: Option, + #[serde(rename = "recurring.recurringDetailReference")] + recurring_detail_reference: Option, + #[serde(rename = "recurring.shopperReference")] + recurring_shopper_reference: Option, + network_tx_reference: Option, } #[derive(Debug, Serialize)] @@ -93,6 +101,8 @@ pub struct AdyenPaymentRequest<'a> { #[serde(skip_serializing_if = "Option::is_none")] recurring_processing_model: Option, additional_data: Option, + shopper_reference: Option, + store_payment_method: Option, shopper_name: Option, shopper_locale: Option, shopper_email: Option>, @@ -200,6 +210,7 @@ pub struct AdyenResponse { merchant_reference: String, refusal_reason: Option, refusal_reason_code: Option, + additional_data: Option, } #[derive(Debug, Clone, Deserialize)] @@ -252,6 +263,7 @@ pub enum AdyenPaymentMethod<'a> { Giropay(Box), Gpay(Box), Ideal(Box>), + Mandate(Box), Mbway(Box), MobilePay(Box), OnlineBankingCzechRepublic(Box), @@ -484,6 +496,14 @@ pub struct BankRedirectionWithIssuer<'a> { issuer: &'a str, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdyenMandate { + #[serde(rename = "type")] + payment_type: PaymentType, + stored_payment_method_id: String, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdyenCard { @@ -493,6 +513,16 @@ pub struct AdyenCard { expiry_month: Secret, expiry_year: Secret, cvc: Option>, + brand: Option, //Mandatory for mandate using network_txns_id + network_payment_reference: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CardBrand { + Visa, + MC, + Amex, } #[derive(Default, Debug, Serialize, Deserialize)] @@ -603,6 +633,8 @@ pub enum PaymentType { Scheme, #[serde(rename = "directEbanking")] Sofort, + #[serde(rename = "networkToken")] + NetworkToken, Trustly, Walley, #[serde(rename = "wechatpayWeb")] @@ -690,24 +722,33 @@ impl TryFrom<&types::ConnectorAuthType> for AdyenAuthType { impl<'a> TryFrom<&types::PaymentsAuthorizeRouterData> for AdyenPaymentRequest<'a> { type Error = Error; fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { - match item.request.payment_method_data { - api_models::payments::PaymentMethodData::Card(ref card) => { - AdyenPaymentRequest::try_from((item, card)) - } - api_models::payments::PaymentMethodData::Wallet(ref wallet) => { - AdyenPaymentRequest::try_from((item, wallet)) - } - api_models::payments::PaymentMethodData::PayLater(ref pay_later) => { - AdyenPaymentRequest::try_from((item, pay_later)) - } - api_models::payments::PaymentMethodData::BankRedirect(ref bank_redirect) => { - AdyenPaymentRequest::try_from((item, bank_redirect)) - } - _ => Err(errors::ConnectorError::NotSupported { - payment_method: format!("{:?}", item.request.payment_method_type), - connector: "Adyen", - payment_experience: api_models::enums::PaymentExperience::RedirectToUrl.to_string(), - })?, + match item + .request + .mandate_id + .to_owned() + .and_then(|mandate_ids| mandate_ids.mandate_reference_id) + { + Some(mandate_ref) => AdyenPaymentRequest::try_from((item, mandate_ref)), + None => match item.request.payment_method_data { + api_models::payments::PaymentMethodData::Card(ref card) => { + AdyenPaymentRequest::try_from((item, card)) + } + api_models::payments::PaymentMethodData::Wallet(ref wallet) => { + AdyenPaymentRequest::try_from((item, wallet)) + } + api_models::payments::PaymentMethodData::PayLater(ref pay_later) => { + AdyenPaymentRequest::try_from((item, pay_later)) + } + api_models::payments::PaymentMethodData::BankRedirect(ref bank_redirect) => { + AdyenPaymentRequest::try_from((item, bank_redirect)) + } + _ => Err(errors::ConnectorError::NotSupported { + payment_method: format!("{:?}", item.request.payment_method_type), + connector: "Adyen", + payment_experience: api_models::enums::PaymentExperience::RedirectToUrl + .to_string(), + })?, + }, } } } @@ -720,15 +761,22 @@ impl From<&types::PaymentsAuthorizeRouterData> for AdyenShopperInteraction { } } } +type RecurringDetails = (Option, Option, Option); fn get_recurring_processing_model( item: &types::PaymentsAuthorizeRouterData, -) -> Option { +) -> Result { match item.request.setup_future_usage { Some(storage_enums::FutureUsage::OffSession) => { - Some(AdyenRecurringModel::UnscheduledCardOnFile) + let customer_id = item.get_customer_id()?; + let shopper_reference = format!("{}_{}", item.merchant_id, customer_id); + Ok(( + Some(AdyenRecurringModel::UnscheduledCardOnFile), + Some(true), + Some(shopper_reference), + )) } - _ => None, + _ => Ok((None, None, None)), } } @@ -757,8 +805,11 @@ fn get_browser_info(item: &types::PaymentsAuthorizeRouterData) -> Option Option { match item.request.capture_method { Some(storage_models::enums::CaptureMethod::Manual) => Some(AdditionalData { - authorisation_type: AuthType::PreAuth, - manual_capture: true, + authorisation_type: Some(AuthType::PreAuth), + manual_capture: Some(true), + network_tx_reference: None, + recurring_detail_reference: None, + recurring_shopper_reference: None, }), _ => None, } @@ -843,11 +894,25 @@ impl<'a> TryFrom<&api::Card> for AdyenPaymentMethod<'a> { expiry_month: card.card_exp_month.clone(), expiry_year: card.card_exp_year.clone(), cvc: Some(card.card_cvc.clone()), + brand: None, + network_payment_reference: None, }; Ok(AdyenPaymentMethod::AdyenCard(Box::new(adyen_card))) } } +impl TryFrom<&utils::CardIssuer> for CardBrand { + type Error = Error; + fn try_from(card_issuer: &utils::CardIssuer) -> Result { + match card_issuer { + utils::CardIssuer::AmericanExpress => Ok(Self::Amex), + utils::CardIssuer::Master => Ok(Self::MC), + utils::CardIssuer::Visa => Ok(Self::Visa), + _ => Err(errors::ConnectorError::NotImplemented("CardBrand".to_string()).into()), + } + } +} + impl<'a> TryFrom<&api::WalletData> for AdyenPaymentMethod<'a> { type Error = Error; fn try_from(wallet_data: &api::WalletData) -> Result { @@ -1022,6 +1087,81 @@ impl<'a> TryFrom<&api_models::payments::BankRedirectData> for AdyenPaymentMethod } } +impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)> + for AdyenPaymentRequest<'a> +{ + type Error = Error; + fn try_from( + value: (&types::PaymentsAuthorizeRouterData, MandateReferenceId), + ) -> Result { + let (item, mandate_ref_id) = value; + let amount = get_amount_data(item); + let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?; + let shopper_interaction = AdyenShopperInteraction::from(item); + let (recurring_processing_model, store_payment_method, shopper_reference) = + get_recurring_processing_model(item)?; + let browser_info = get_browser_info(item); + let additional_data = get_additional_data(item); + let return_url = item.request.get_return_url()?; + let payment_method = match mandate_ref_id { + MandateReferenceId::ConnectorMandateId(connector_mandate_id) => { + let adyen_mandate = AdyenMandate { + payment_type: PaymentType::Scheme, + stored_payment_method_id: connector_mandate_id, + }; + Ok::, Self::Error>(AdyenPaymentMethod::Mandate(Box::new( + adyen_mandate, + ))) + } + MandateReferenceId::NetworkMandateId(network_mandate_id) => { + match item.request.payment_method_data { + api::PaymentMethodData::Card(ref card) => { + let card_issuer = card.get_card_issuer()?; + let brand = CardBrand::try_from(&card_issuer)?; + let adyen_card = AdyenCard { + payment_type: PaymentType::Scheme, + number: card.card_number.clone(), + expiry_month: card.card_exp_month.clone(), + expiry_year: card.card_exp_year.clone(), + cvc: None, + brand: Some(brand), + network_payment_reference: Some(network_mandate_id), + }; + Ok(AdyenPaymentMethod::AdyenCard(Box::new(adyen_card))) + } + _ => Err(errors::ConnectorError::NotSupported { + payment_method: format!("mandate_{:?}", item.payment_method), + connector: "Adyen", + payment_experience: api_models::enums::PaymentExperience::RedirectToUrl + .to_string(), + })?, + } + } + }?; + Ok(AdyenPaymentRequest { + amount, + merchant_account: auth_type.merchant_account, + payment_method, + reference: item.payment_id.to_string(), + return_url, + shopper_interaction, + recurring_processing_model, + browser_info, + additional_data, + telephone_number: None, + shopper_name: None, + shopper_email: None, + shopper_locale: None, + billing_address: None, + delivery_address: None, + country_code: None, + line_items: None, + shopper_reference, + store_payment_method, + }) + } +} + impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPaymentRequest<'a> { type Error = Error; fn try_from( @@ -1031,7 +1171,8 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPay let amount = get_amount_data(item); let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?; let shopper_interaction = AdyenShopperInteraction::from(item); - let recurring_processing_model = get_recurring_processing_model(item); + let (recurring_processing_model, store_payment_method, shopper_reference) = + get_recurring_processing_model(item)?; let browser_info = get_browser_info(item); let additional_data = get_additional_data(item); let return_url = item.request.get_return_url()?; @@ -1054,6 +1195,8 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPay delivery_address: None, country_code: None, line_items: None, + shopper_reference, + store_payment_method, }) } } @@ -1075,7 +1218,8 @@ impl<'a> let amount = get_amount_data(item); let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?; let shopper_interaction = AdyenShopperInteraction::from(item); - let recurring_processing_model = get_recurring_processing_model(item); + let (recurring_processing_model, store_payment_method, shopper_reference) = + get_recurring_processing_model(item)?; let browser_info = get_browser_info(item); let additional_data = get_additional_data(item); let return_url = item.request.get_return_url()?; @@ -1101,6 +1245,8 @@ impl<'a> delivery_address: None, country_code: country, line_items, + shopper_reference, + store_payment_method, }) } } @@ -1142,7 +1288,8 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::WalletData)> let additional_data = get_additional_data(item); let payment_method = AdyenPaymentMethod::try_from(wallet_data)?; let shopper_interaction = AdyenShopperInteraction::from(item); - let recurring_processing_model = get_recurring_processing_model(item); + let (recurring_processing_model, store_payment_method, shopper_reference) = + get_recurring_processing_model(item)?; let return_url = item.request.get_return_url()?; Ok(AdyenPaymentRequest { amount, @@ -1162,6 +1309,8 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::WalletData)> delivery_address: None, country_code: None, line_items: None, + shopper_reference, + store_payment_method, }) } } @@ -1180,7 +1329,8 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::PayLaterData)> let additional_data = get_additional_data(item); let payment_method = AdyenPaymentMethod::try_from(paylater_data)?; let shopper_interaction = AdyenShopperInteraction::from(item); - let recurring_processing_model = get_recurring_processing_model(item); + let (recurring_processing_model, store_payment_method, shopper_reference) = + get_recurring_processing_model(item)?; let return_url = item.request.get_return_url()?; let shopper_name = get_shopper_name(item); let shopper_email = item.request.email.clone(); @@ -1207,6 +1357,8 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::PayLaterData)> delivery_address, country_code, line_items, + shopper_reference, + store_payment_method, }) } } @@ -1245,6 +1397,7 @@ impl TryFrom> redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) @@ -1279,12 +1432,20 @@ pub fn get_adyen_response( } else { None }; + let mandate_reference = response + .additional_data + .as_ref() + .and_then(|additional_data| additional_data.recurring_detail_reference.to_owned()); + let network_txn_id = response + .additional_data + .and_then(|additional_data| additional_data.network_tx_reference); let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(response.psp_reference), redirection_data: None, - mandate_reference: None, + mandate_reference, connector_metadata: None, + network_txn_id, }; Ok((status, error, payments_response_data)) } @@ -1338,6 +1499,7 @@ pub fn get_redirection_response( redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }; Ok((status, error, payments_response_data)) } @@ -1428,6 +1590,7 @@ impl TryFrom> redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), amount_captured, ..item.data @@ -1686,6 +1849,7 @@ impl From for AdyenResponse { }), refusal_reason: None, refusal_reason_code: None, + additional_data: None, } } } diff --git a/crates/router/src/connector/airwallex/transformers.rs b/crates/router/src/connector/airwallex/transformers.rs index 9283c7eb70..86831e592c 100644 --- a/crates/router/src/connector/airwallex/transformers.rs +++ b/crates/router/src/connector/airwallex/transformers.rs @@ -382,6 +382,7 @@ impl redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index f999243183..8f26534058 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -3,7 +3,7 @@ use error_stack::ResultExt; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::RefundsRequestData, + connector::utils::{CardData, RefundsRequestData}, core::errors, types::{self, api, storage::enums}, utils::OptionExt, @@ -68,34 +68,76 @@ enum PaymentDetails { BankRedirect, } -impl TryFrom for PaymentDetails { - type Error = error_stack::Report; - fn try_from(value: api_models::payments::PaymentMethodData) -> Result { - match value { - api::PaymentMethodData::Card(ref ccard) => { - Ok(Self::CreditCard(CreditCardDetails { - card_number: ccard.card_number.clone(), - // expiration_date: format!("{expiry_year}-{expiry_month}").into(), - expiration_date: ccard - .card_exp_month - .clone() - .zip(ccard.card_exp_year.clone()) - .map(|(expiry_month, expiry_year)| format!("{expiry_year}-{expiry_month}")), - card_code: Some(ccard.card_cvc.clone()), - })) +fn get_pm_and_subsequent_auth_detail( + item: &types::PaymentsAuthorizeRouterData, +) -> Result< + ( + PaymentDetails, + Option, + Option, + ), + error_stack::Report, +> { + match item + .request + .mandate_id + .to_owned() + .and_then(|mandate_ids| mandate_ids.mandate_reference_id) + { + Some(api_models::payments::MandateReferenceId::NetworkMandateId( + original_network_trans_id, + )) => { + let processing_options = Some(ProcessingOptions { + is_subsequent_auth: true, + }); + let subseuent_auth_info = Some(SubsequentAuthInformation { + original_network_trans_id, + reason: Reason::Resubmission, + }); + match item.request.payment_method_data { + api::PaymentMethodData::Card(ref ccard) => { + let payment_details = PaymentDetails::CreditCard(CreditCardDetails { + card_number: ccard.card_number.clone(), + expiration_date: ccard.get_expiry_date_as_yyyymm("-"), + card_code: None, + }); + Ok((payment_details, processing_options, subseuent_auth_info)) + } + _ => Err(errors::ConnectorError::NotSupported { + payment_method: format!("{:?}", item.request.payment_method_data), + connector: "AuthorizeDotNet", + payment_experience: api_models::enums::PaymentExperience::RedirectToUrl + .to_string(), + })?, + } + } + _ => match item.request.payment_method_data { + api::PaymentMethodData::Card(ref ccard) => { + Ok(( + PaymentDetails::CreditCard(CreditCardDetails { + card_number: ccard.card_number.clone(), + // expiration_date: format!("{expiry_year}-{expiry_month}").into(), + expiration_date: ccard.get_expiry_date_as_yyyymm("-"), + card_code: Some(ccard.card_cvc.clone()), + }), + None, + None, + )) + } + api::PaymentMethodData::PayLater(_) => Ok((PaymentDetails::Klarna, None, None)), + api::PaymentMethodData::Wallet(_) => Ok((PaymentDetails::Wallet, None, None)), + api::PaymentMethodData::BankRedirect(_) => { + Ok((PaymentDetails::BankRedirect, None, None)) } - api::PaymentMethodData::PayLater(_) => Ok(Self::Klarna), - api::PaymentMethodData::Wallet(_) => Ok(Self::Wallet), - api::PaymentMethodData::BankRedirect(_) => Ok(Self::BankRedirect), api::PaymentMethodData::Crypto(_) | api::PaymentMethodData::BankDebit(_) => { Err(errors::ConnectorError::NotSupported { - payment_method: format!("{value:?}"), + payment_method: format!("{:?}", item.request.payment_method_data), connector: "AuthorizeDotNet", payment_experience: api_models::enums::PaymentExperience::RedirectToUrl .to_string(), })? } - } + }, } } @@ -106,9 +148,36 @@ struct TransactionRequest { amount: i64, currency_code: String, payment: PaymentDetails, + processing_options: Option, + subsequent_auth_information: Option, authorization_indicator_type: Option, } +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ProcessingOptions { + is_subsequent_auth: bool, +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SubsequentAuthInformation { + original_network_trans_id: String, + // original_auth_amount: String, Required for Discover, Diners Club, JCB, and China Union Pay transactions. + reason: Reason, +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum Reason { + Resubmission, + #[serde(rename = "delayedCharge")] + DelayedCharge, + Reauthorization, + #[serde(rename = "noShow")] + NoShow, +} + #[derive(Debug, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] struct AuthorizationIndicator { @@ -168,7 +237,8 @@ impl From for AuthorizationType { impl TryFrom<&types::PaymentsAuthorizeRouterData> for CreateTransactionRequest { type Error = error_stack::Report; fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { - let payment_details = PaymentDetails::try_from(item.request.payment_method_data.clone())?; + let (payment_details, processing_options, subsequent_auth_information) = + get_pm_and_subsequent_auth_detail(item)?; let authorization_indicator_type = item.request.capture_method.map(|c| AuthorizationIndicator { authorization_indicator: c.into(), @@ -178,6 +248,8 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for CreateTransactionRequest { amount: item.request.amount, payment: payment_details, currency_code: item.request.currency.to_string(), + processing_options, + subsequent_auth_information, authorization_indicator_type, }; @@ -271,6 +343,7 @@ pub struct TransactionResponse { auth_code: String, #[serde(rename = "transId")] transaction_id: String, + network_trans_id: Option, pub(super) account_number: Option, pub(super) errors: Option>, } @@ -340,6 +413,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: metadata, + network_txn_id: item.response.transaction_response.network_trans_id, }), }, ..item.data @@ -616,6 +690,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), status: payment_status, ..item.data diff --git a/crates/router/src/connector/bambora/transformers.rs b/crates/router/src/connector/bambora/transformers.rs index 70f6d231a0..443e703e66 100644 --- a/crates/router/src/connector/bambora/transformers.rs +++ b/crates/router/src/connector/bambora/transformers.rs @@ -205,6 +205,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }), @@ -227,6 +228,7 @@ impl .into_report() .change_context(errors::ConnectorError::ResponseHandlingFailed)?, ), + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/bluesnap/transformers.rs b/crates/router/src/connector/bluesnap/transformers.rs index d091ccaa27..c2f851ce61 100644 --- a/crates/router/src/connector/bluesnap/transformers.rs +++ b/crates/router/src/connector/bluesnap/transformers.rs @@ -286,6 +286,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index c2ab2b9a5c..dd9c03dafe 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -221,6 +221,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 4cad762a3d..2e9a8f2b82 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -350,6 +350,7 @@ impl TryFrom> redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) @@ -377,6 +378,7 @@ impl TryFrom> redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) @@ -419,6 +421,7 @@ impl TryFrom> redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), status: response.into(), ..item.data @@ -490,6 +493,7 @@ impl TryFrom> redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), status, amount_captured, diff --git a/crates/router/src/connector/coinbase/transformers.rs b/crates/router/src/connector/coinbase/transformers.rs index 627ae4dadd..f3d43857ab 100644 --- a/crates/router/src/connector/coinbase/transformers.rs +++ b/crates/router/src/connector/coinbase/transformers.rs @@ -144,6 +144,7 @@ impl redirection_data: Some(redirection_data), mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), |context| { Ok(types::PaymentsResponseData::TransactionUnresolvedResponse{ diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index fab649e19c..85377cc3fb 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -317,6 +317,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), }, ..item.data @@ -380,6 +381,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/dlocal/transformers.rs b/crates/router/src/connector/dlocal/transformers.rs index 3952b57930..c54fa3b31c 100644 --- a/crates/router/src/connector/dlocal/transformers.rs +++ b/crates/router/src/connector/dlocal/transformers.rs @@ -271,6 +271,7 @@ impl redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }; Ok(Self { status: enums::AttemptStatus::from(item.response.status), @@ -307,6 +308,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) @@ -340,6 +342,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) @@ -372,6 +375,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/fiserv/transformers.rs b/crates/router/src/connector/fiserv/transformers.rs index 5e5027fe42..b52f5d259b 100644 --- a/crates/router/src/connector/fiserv/transformers.rs +++ b/crates/router/src/connector/fiserv/transformers.rs @@ -317,6 +317,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) @@ -350,6 +351,7 @@ impl TryFrom redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/globalpay/transformers.rs b/crates/router/src/connector/globalpay/transformers.rs index d76b09b5ce..15f3a299d4 100644 --- a/crates/router/src/connector/globalpay/transformers.rs +++ b/crates/router/src/connector/globalpay/transformers.rs @@ -214,6 +214,7 @@ fn get_payment_response( redirection_data, mandate_reference, connector_metadata: None, + network_txn_id: None, }), } } @@ -370,11 +371,14 @@ fn get_return_url(item: &types::PaymentsAuthorizeRouterData) -> Option { type MandateDetails = (Option, Option, Option); fn get_mandate_details(item: &types::PaymentsAuthorizeRouterData) -> Result { Ok(if item.request.is_mandate_payment() { - let connector_mandate_id = item - .request - .mandate_id - .as_ref() - .and_then(|mandate_ids| mandate_ids.connector_mandate_id.clone()); + let connector_mandate_id = item.request.mandate_id.as_ref().and_then(|mandate_ids| { + match mandate_ids.mandate_reference_id.clone() { + Some(api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_mandate_id, + )) => Some(connector_mandate_id), + _ => None, + } + }); ( Some(match item.request.off_session { Some(true) => Initiator::Merchant, diff --git a/crates/router/src/connector/klarna/transformers.rs b/crates/router/src/connector/klarna/transformers.rs index e065014efb..62ec396121 100644 --- a/crates/router/src/connector/klarna/transformers.rs +++ b/crates/router/src/connector/klarna/transformers.rs @@ -120,6 +120,7 @@ impl TryFrom> redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), status: item.response.fraud_status.into(), ..item.data diff --git a/crates/router/src/connector/mollie/transformers.rs b/crates/router/src/connector/mollie/transformers.rs index 4fb658cb16..1d0c8ecd52 100644 --- a/crates/router/src/connector/mollie/transformers.rs +++ b/crates/router/src/connector/mollie/transformers.rs @@ -333,6 +333,7 @@ impl redirection_data: url, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/multisafepay/transformers.rs b/crates/router/src/connector/multisafepay/transformers.rs index 3aadcd8de6..3466392473 100644 --- a/crates/router/src/connector/multisafepay/transformers.rs +++ b/crates/router/src/connector/multisafepay/transformers.rs @@ -345,7 +345,12 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques .request .mandate_id .clone() - .and_then(|mandate_ids| mandate_ids.connector_mandate_id), + .and_then(|mandate_ids| match mandate_ids.mandate_reference_id { + Some(api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_mandate_id, + )) => Some(connector_mandate_id), + _ => None, + }), days_active: Some(30), seconds_active: Some(259200), var1: None, @@ -469,6 +474,7 @@ impl .payment_details .and_then(|payment_details| payment_details.recurring_id), connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/nexinets/transformers.rs b/crates/router/src/connector/nexinets/transformers.rs index fadae49747..7c8db92453 100644 --- a/crates/router/src/connector/nexinets/transformers.rs +++ b/crates/router/src/connector/nexinets/transformers.rs @@ -355,6 +355,7 @@ impl redirection_data, mandate_reference, connector_metadata: Some(connector_metadata), + network_txn_id: None, }), ..item.data }) @@ -436,6 +437,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: Some(connector_metadata), + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index cb85b337e6..b80c599db2 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -1156,6 +1156,7 @@ where } else { None }, + network_txn_id: None, }) }, ..item.data diff --git a/crates/router/src/connector/opennode.rs b/crates/router/src/connector/opennode.rs index cc665abe43..fa66aa5e12 100644 --- a/crates/router/src/connector/opennode.rs +++ b/crates/router/src/connector/opennode.rs @@ -27,6 +27,7 @@ impl api::Payment for Opennode {} impl api::PaymentSession for Opennode {} impl api::PaymentToken for Opennode {} impl api::ConnectorAccessToken for Opennode {} + impl api::PreVerify for Opennode {} impl api::PaymentAuthorize for Opennode {} impl api::PaymentSync for Opennode {} diff --git a/crates/router/src/connector/opennode/transformers.rs b/crates/router/src/connector/opennode/transformers.rs index 4458b13eb0..51466eade8 100644 --- a/crates/router/src/connector/opennode/transformers.rs +++ b/crates/router/src/connector/opennode/transformers.rs @@ -110,6 +110,7 @@ impl redirection_data: Some(redirection_data), mandate_reference: None, connector_metadata: None, + network_txn_id: None, }) } else { Ok(types::PaymentsResponseData::TransactionUnresolvedResponse { diff --git a/crates/router/src/connector/payeezy/transformers.rs b/crates/router/src/connector/payeezy/transformers.rs index dd4d9b5aa3..428cc12bba 100644 --- a/crates/router/src/connector/payeezy/transformers.rs +++ b/crates/router/src/connector/payeezy/transformers.rs @@ -127,11 +127,14 @@ fn get_transaction_type_and_stored_creds( (PayeezyTransactionType, Option), error_stack::Report, > { - let connector_mandate_id = item - .request - .mandate_id - .as_ref() - .and_then(|mandate_ids| mandate_ids.connector_mandate_id.clone()); + let connector_mandate_id = item.request.mandate_id.as_ref().and_then(|mandate_ids| { + match mandate_ids.mandate_reference_id.clone() { + Some(api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_mandate_id, + )) => Some(connector_mandate_id), + _ => None, + } + }); let (transaction_type, stored_credentials) = if is_mandate_payment(item, connector_mandate_id.as_ref()) { // Mandate payment @@ -353,6 +356,7 @@ impl redirection_data: None, mandate_reference, connector_metadata: metadata, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 6c35996c9d..c1115a5243 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -385,6 +385,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: Some(connector_meta), + network_txn_id: None, }), ..item.data }) @@ -433,6 +434,7 @@ impl ))), mandate_reference: None, connector_metadata: Some(connector_meta), + network_txn_id: None, }), ..item.data }) @@ -460,6 +462,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) @@ -547,6 +550,7 @@ impl TryFrom> order_id: item.data.request.connector_transaction_id.clone(), psync_flow: PaypalPaymentIntent::Capture })), + network_txn_id: None, }), amount_captured: Some(amount_captured), ..item.data @@ -592,6 +596,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/payu/transformers.rs b/crates/router/src/connector/payu/transformers.rs index b5a1cdbdd3..7e7cc8abf4 100644 --- a/crates/router/src/connector/payu/transformers.rs +++ b/crates/router/src/connector/payu/transformers.rs @@ -198,6 +198,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), amount_captured: None, ..item.data @@ -248,6 +249,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), amount_captured: None, ..item.data @@ -326,6 +328,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), amount_captured: None, ..item.data @@ -454,6 +457,7 @@ impl redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), amount_captured: Some( order diff --git a/crates/router/src/connector/rapyd/transformers.rs b/crates/router/src/connector/rapyd/transformers.rs index 4e9de6121b..63a0075515 100644 --- a/crates/router/src/connector/rapyd/transformers.rs +++ b/crates/router/src/connector/rapyd/transformers.rs @@ -402,6 +402,7 @@ impl redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ) } diff --git a/crates/router/src/connector/shift4/transformers.rs b/crates/router/src/connector/shift4/transformers.rs index 77fbe1cfe8..4baf9882d1 100644 --- a/crates/router/src/connector/shift4/transformers.rs +++ b/crates/router/src/connector/shift4/transformers.rs @@ -462,6 +462,7 @@ impl .into_report() .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, ), + network_txn_id: None, }), ..item.data }) @@ -500,6 +501,7 @@ impl .map(|url| services::RedirectForm::from((url, services::Method::Get))), mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 98b5c0560e..2f74ad756c 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -84,6 +84,117 @@ impl // Not Implemented (R) } +impl api::ConnectorCustomer for Stripe {} + +impl + services::ConnectorIntegration< + api::CreateConnectorCustomer, + types::ConnectorCustomerData, + types::PaymentsResponseData, + > for Stripe +{ + fn get_headers( + &self, + req: &types::ConnectorCustomerRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + types::ConnectorCustomerType::get_content_type(self).to_string(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::ConnectorCustomerRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "v1/customers")) + } + + fn get_request_body( + &self, + req: &types::ConnectorCustomerRouterData, + ) -> CustomResult, errors::ConnectorError> { + let connector_request = stripe::CustomerRequest::try_from(req)?; + let stripe_req = utils::Encode::::url_encode(&connector_request) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + Ok(Some(stripe_req)) + } + + fn build_request( + &self, + req: &types::ConnectorCustomerRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::ConnectorCustomerType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::ConnectorCustomerType::get_headers( + self, req, connectors, + )?) + .body(types::ConnectorCustomerType::get_request_body(self, req)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::ConnectorCustomerRouterData, + res: types::Response, + ) -> CustomResult + where + types::PaymentsResponseData: Clone, + { + let response: stripe::StripeCustomerResponse = res + .response + .parse_struct("StripeCustomerResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + let response: stripe::ErrorResponse = res + .response + .parse_struct("ErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(types::ErrorResponse { + status_code: res.status_code, + code: response + .error + .code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .error + .message + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: None, + }) + } +} + impl api::PaymentToken for Stripe {} impl diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index cc0010a43f..6d203610ac 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -94,6 +94,8 @@ pub struct PaymentIntentRequest { pub return_url: String, pub confirm: bool, pub mandate: Option, + pub payment_method: Option, + pub customer: Option, #[serde(flatten)] pub setup_mandate_details: Option, pub description: Option, @@ -104,6 +106,8 @@ pub struct PaymentIntentRequest { #[serde(flatten)] pub payment_data: Option, pub capture_method: StripeCaptureMethod, + pub payment_method_options: Option, // For mandate txns using network_txns_id, needs to be validated + pub setup_future_usage: Option, } #[derive(Debug, Eq, PartialEq, Serialize)] @@ -116,10 +120,12 @@ pub struct SetupIntentRequest { pub metadata_txn_uuid: String, pub confirm: bool, pub usage: Option, + pub customer: Option, pub off_session: Option, pub return_url: Option, #[serde(flatten)] pub payment_data: StripePaymentMethodData, + pub payment_method_options: Option, // For mandate txns using network_txns_id, needs to be validated } #[derive(Debug, Eq, PartialEq, Serialize)] @@ -159,6 +165,23 @@ pub struct StripeTokenResponse { pub object: String, } +#[derive(Debug, Eq, PartialEq, Serialize)] +pub struct CustomerRequest { + pub description: Option, + pub email: Option>, + pub phone: Option>, + pub name: Option, +} + +#[derive(Debug, Eq, PartialEq, Deserialize)] +pub struct StripeCustomerResponse { + pub id: String, + pub description: Option, + pub email: Option>, + pub phone: Option>, + pub name: Option, +} + #[derive(Debug, Eq, PartialEq, Serialize)] #[serde(untagged)] pub enum StripeBankName { @@ -824,15 +847,31 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { }, None => StripeShippingAddress::default(), }; + let mut payment_method_options = None; - let (mut payment_data, mandate, billing_address) = { + let (mut payment_data, payment_method, billing_address) = { match item .request .mandate_id .clone() - .and_then(|mandate_ids| mandate_ids.connector_mandate_id) + .and_then(|mandate_ids| mandate_ids.mandate_reference_id) { - None => { + Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_id)) => { + (None, Some(mandate_id), StripeBillingAddress::default()) + } + Some(api_models::payments::MandateReferenceId::NetworkMandateId( + network_transaction_id, + )) => { + payment_method_options = Some(StripePaymentMethodOptions::Card { + mandate_options: None, + network_transaction_id: None, + mit_exemption: Some(MitExemption { + network_transaction_id, + }), + }); + (None, None, StripeBillingAddress::default()) + } + _ => { let (payment_method_data, payment_method_type, billing_address) = create_stripe_payment_method( item.request.payment_method_type.as_ref(), @@ -848,7 +887,6 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { (Some(payment_method_data), None, billing_address) } - Some(mandate_id) => (None, Some(mandate_id), StripeBillingAddress::default()), } }; @@ -902,8 +940,12 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { billing: billing_address, capture_method: StripeCaptureMethod::from(item.request.capture_method), payment_data, - mandate, + mandate: None, + payment_method_options, + payment_method, + customer: item.connector_customer.to_owned(), setup_mandate_details, + setup_future_usage: item.request.setup_future_usage, }) } } @@ -932,6 +974,8 @@ impl TryFrom<&types::VerifyRouterData> for SetupIntentRequest { return_url: item.return_url.clone(), off_session: item.request.off_session, usage: item.request.setup_future_usage, + payment_method_options: None, + customer: item.connector_customer.to_owned(), }) } } @@ -951,6 +995,18 @@ impl TryFrom<&types::TokenizationRouterData> for TokenRequest { } } +impl TryFrom<&types::ConnectorCustomerRouterData> for CustomerRequest { + type Error = error_stack::Report; + fn try_from(item: &types::ConnectorCustomerRouterData) -> Result { + Ok(Self { + description: item.request.description.to_owned(), + email: item.request.email.to_owned(), + phone: item.request.phone.to_owned(), + name: item.request.name.to_owned(), + }) + } +} + #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct StripeMetadata { pub order_id: String, @@ -1004,6 +1060,7 @@ pub struct PaymentIntentResponse { pub client_secret: Secret, pub created: i32, pub customer: Option, + pub payment_method: Option, pub description: Option, pub statement_descriptor: Option, pub statement_descriptor_suffix: Option, @@ -1011,6 +1068,7 @@ pub struct PaymentIntentResponse { pub next_action: Option, pub payment_method_options: Option, pub last_payment_error: Option, + pub latest_attempt: Option, //need a merchant to test this } #[derive(Debug, Default, Eq, PartialEq, Deserialize)] @@ -1100,11 +1158,13 @@ pub struct SetupIntentResponse { pub status: StripePaymentStatus, // Change to SetupStatus pub client_secret: Secret, pub customer: Option, + pub payment_method: Option, pub statement_descriptor: Option, pub statement_descriptor_suffix: Option, pub metadata: StripeMetadata, pub next_action: Option, pub payment_method_options: Option, + pub latest_attempt: Option, } impl @@ -1119,15 +1179,19 @@ impl services::RedirectForm::from((next_action_response.get_url(), services::Method::Get)) }); - let mandate_reference = - item.response + //Note: we might have to call retrieve_setup_intent to get the network_transaction_id in case its not sent in PaymentIntentResponse + // Or we identify the mandate txns before hand and always call SetupIntent in case of mandate payment call + let network_txn_id = item.response.latest_attempt.and_then(|latest_attempt| { + latest_attempt .payment_method_options .and_then(|payment_method_options| match payment_method_options { StripePaymentMethodOptions::Card { - mandate_options, .. - } => mandate_options.map(|mandate_options| mandate_options.reference), + network_transaction_id, + .. + } => network_transaction_id, _ => None, - }); + }) + }); Ok(Self { status: enums::AttemptStatus::from(item.response.status), @@ -1138,8 +1202,9 @@ impl response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), redirection_data, - mandate_reference, + mandate_reference: item.response.payment_method, connector_metadata: None, + network_txn_id, }), amount_captured: Some(item.response.amount_received), ..item.data @@ -1209,6 +1274,7 @@ impl redirection_data, mandate_reference, connector_metadata: None, + network_txn_id: None, }), Err, ); @@ -1234,23 +1300,26 @@ impl services::RedirectForm::from((next_action_response.get_url(), services::Method::Get)) }); - let mandate_reference = - item.response + let network_txn_id = item.response.latest_attempt.and_then(|latest_attempt| { + latest_attempt .payment_method_options .and_then(|payment_method_options| match payment_method_options { StripePaymentMethodOptions::Card { - mandate_options, .. - } => mandate_options.map(|mandate_option| mandate_option.reference), + network_transaction_id, + .. + } => network_transaction_id, _ => None, - }); + }) + }); Ok(Self { status: enums::AttemptStatus::from(item.response.status), response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), redirection_data, - mandate_reference, + mandate_reference: item.response.payment_method, connector_metadata: None, + network_txn_id, }), ..item.data }) @@ -1481,12 +1550,14 @@ impl TryFrom<&types::PaymentsCancelRouterData> for CancelRequest { } } -#[derive(Deserialize, Debug, Clone, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[non_exhaustive] #[serde(rename_all = "snake_case")] pub enum StripePaymentMethodOptions { Card { mandate_options: Option, + network_transaction_id: Option, + mit_exemption: Option, // To be used for MIT mandate txns }, Klarna {}, Affirm {}, @@ -1504,9 +1575,19 @@ pub enum StripePaymentMethodOptions { #[serde(rename = "bacs_debit")] Bacs {}, } + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MitExemption { + pub network_transaction_id: String, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] +pub struct LatestAttempt { + pub payment_method_options: Option, +} // #[derive(Deserialize, Debug, Clone, Eq, PartialEq)] // pub struct Card -#[derive(serde::Deserialize, Clone, Debug, Default, Eq, PartialEq)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, Eq, PartialEq)] pub struct StripeMandateOptions { reference: String, // Extendable, But only important field to be captured } @@ -1543,6 +1624,23 @@ impl } } +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::PaymentsResponseData::ConnectorCustomerResponse { + connector_customer_id: item.response.id, + }), + ..item.data + }) + } +} + // #[cfg(test)] // mod test_stripe_transformers { // use super::*; diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index e0e639610e..c3cbbc1090 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -580,6 +580,7 @@ fn handle_cards_response( redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }; Ok((status, error, payment_response_data)) } @@ -606,6 +607,7 @@ fn handle_bank_redirects_response( ))), mandate_reference: None, connector_metadata: None, + network_txn_id: None, }; Ok((status, error, payment_response_data)) } @@ -636,6 +638,7 @@ fn handle_bank_redirects_error_response( redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }; Ok((status, error, payment_response_data)) } @@ -676,6 +679,7 @@ fn handle_bank_redirects_sync_response( redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }; Ok((status, error, payment_response_data)) } @@ -696,6 +700,7 @@ pub fn handle_webhook_response( redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }; Ok((status, None, payment_response_data)) } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 1bd446a72c..c2c05df8b7 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -62,6 +62,7 @@ pub trait RouterData { T: serde::de::DeserializeOwned; fn is_three_ds(&self) -> bool; fn get_payment_method_token(&self) -> Result; + fn get_customer_id(&self) -> Result; } impl RouterData for types::RouterData { @@ -145,6 +146,11 @@ impl RouterData for types::RouterData Result { + self.customer_id + .to_owned() + .ok_or_else(missing_field_err("customer_id")) + } } pub trait PaymentsAuthorizeRequestData { @@ -196,14 +202,19 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { fn connector_mandate_id(&self) -> Option { self.mandate_id .as_ref() - .and_then(|mandate_ids| mandate_ids.connector_mandate_id.clone()) + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_mandate_id, + )) => Some(connector_mandate_id.to_string()), + _ => None, + }) } fn is_mandate_payment(&self) -> bool { self.setup_mandate_details.is_some() || self .mandate_id .as_ref() - .and_then(|mandate_ids| mandate_ids.connector_mandate_id.as_ref()) + .and_then(|mandate_ids| mandate_ids.mandate_reference_id.as_ref()) .is_some() } fn get_webhook_url(&self) -> Result { diff --git a/crates/router/src/connector/worldline/transformers.rs b/crates/router/src/connector/worldline/transformers.rs index 6b580815b1..4b34d05431 100644 --- a/crates/router/src/connector/worldline/transformers.rs +++ b/crates/router/src/connector/worldline/transformers.rs @@ -352,6 +352,7 @@ impl TryFrom TryFrom> redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/zen/transformers.rs b/crates/router/src/connector/zen/transformers.rs index df077c3bbf..c273743b24 100644 --- a/crates/router/src/connector/zen/transformers.rs +++ b/crates/router/src/connector/zen/transformers.rs @@ -295,6 +295,7 @@ impl redirection_data, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }), ..item.data }) diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index 4462a1e3e8..345245ab84 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -69,6 +69,7 @@ pub async fn create_customer( description: customer_data.description, phone_country_code: customer_data.phone_country_code, metadata: customer_data.metadata, + connector_customer: None, }; let customer = match db.insert_customer(new_customer).await { @@ -205,6 +206,7 @@ pub async fn delete_customer( description: Some(REDACTED.to_string()), phone_country_code: Some(REDACTED.to_string()), metadata: None, + connector_customer: None, }; db.update_customer_by_customer_id_merchant_id( req.customer_id.clone(), @@ -282,6 +284,7 @@ pub async fn update_customer( phone_country_code: update_customer.phone_country_code, metadata: update_customer.metadata, description: update_customer.description, + connector_customer: None, }, ) .await diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index d88f3f0ae9..25f7f3ab69 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -149,12 +149,13 @@ where None => { if resp.request.get_setup_mandate_details().is_some() { resp.payment_method_id = pm_id.clone(); - let mandate_reference = match resp.response.as_ref().ok() { + let (mandate_reference, network_txn_id) = match resp.response.as_ref().ok() { Some(types::PaymentsResponseData::TransactionResponse { mandate_reference, + network_txn_id, .. - }) => mandate_reference.clone(), - _ => None, + }) => (mandate_reference.clone(), network_txn_id.clone()), + _ => (None, None), }; if let Some(new_mandate_data) = helpers::generate_mandate( @@ -164,13 +165,30 @@ where maybe_customer, pm_id.get_required_value("payment_method_id")?, mandate_reference, + network_txn_id, ) { let connector = new_mandate_data.connector.clone(); logger::debug!("{:?}", new_mandate_data); resp.request .set_mandate_id(api_models::payments::MandateIds { mandate_id: new_mandate_data.mandate_id.clone(), - connector_mandate_id: new_mandate_data.connector_mandate_id.clone(), + mandate_reference_id: new_mandate_data + .connector_mandate_id + .clone() + .map_or( + new_mandate_data.network_transaction_id.clone().map(|id| { + api_models::payments::MandateReferenceId::NetworkMandateId( + id, + ) + }), + |connector_id| { + Some( + api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_id, + ), + ) + }, + ), }); state .store diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index f2a7fdc9b8..4bb67bdb55 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -104,10 +104,19 @@ where ) .await?; - let (payment_data, tokenization_action) = + let (mut payment_data, tokenization_action) = get_connector_tokenization_action(state, &operation, payment_data, &validate_result) .await?; + let updated_customer = call_create_connector_customer( + state, + &connector, + &customer, + &merchant_account, + &mut payment_data, + ) + .await?; + let (operation, mut payment_data) = operation .to_update_tracker()? .update_trackers( @@ -116,6 +125,7 @@ where payment_data, customer.clone(), validate_result.storage_scheme, + updated_customer, ) .await?; @@ -469,7 +479,7 @@ where let stime_connector = Instant::now(); let mut router_data = payment_data - .construct_router_data(state, connector.connector.id(), merchant_account) + .construct_router_data(state, connector.connector.id(), merchant_account, customer) .await?; let add_access_token_result = router_data @@ -539,7 +549,7 @@ where for connector in connectors.iter() { let connector_id = connector.connector.id(); let router_data = payment_data - .construct_router_data(state, connector_id, merchant_account) + .construct_router_data(state, connector_id, merchant_account, customer) .await?; let res = router_data.decide_flows( @@ -583,6 +593,50 @@ where Ok(payment_data) } +pub async fn call_create_connector_customer( + state: &AppState, + connector: &Option, + customer: &Option, + merchant_account: &storage::MerchantAccount, + payment_data: &mut PaymentData, +) -> RouterResult> +where + F: Send + Clone + Sync, + Req: Send + Sync, + + // To create connector flow specific interface data + PaymentData: ConstructFlowSpecificData, + types::RouterData: Feature + Send, + + // To construct connector flow specific api + dyn api::Connector: services::api::ConnectorIntegration, + + // To perform router related operation for PaymentResponse + PaymentResponse: Operation, +{ + match connector { + Some(connector_details) => match connector_details { + api::ConnectorCallType::Single(connector) => { + let router_data = payment_data + .construct_router_data( + state, + connector.connector.id(), + merchant_account, + customer, + ) + .await?; + let (connector_customer, customer_update) = router_data + .create_connector_customer(state, connector, customer) + .await?; + payment_data.connector_customer_id = connector_customer; + Ok(customer_update) + } + api::ConnectorCallType::Multiple(_) => Ok(None), + }, + None => Ok(None), + } +} + fn is_payment_method_tokenization_enabled_for_connector( state: &AppState, connector_name: &str, @@ -766,6 +820,7 @@ where pub email: Option>, pub creds_identifier: Option, pub pm_token: Option, + pub connector_customer_id: Option, } #[derive(Debug, Default)] diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 8f8a447494..ea5f7ad3dc 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -26,6 +26,7 @@ pub trait ConstructFlowSpecificData { state: &AppState, connector_id: &str, merchant_account: &storage::MerchantAccount, + customer: &Option, ) -> RouterResult>; } @@ -68,6 +69,20 @@ pub trait Feature { { Ok(None) } + + async fn create_connector_customer<'a>( + &self, + _state: &AppState, + _connector: &api::ConnectorData, + _customer: &Option, + ) -> RouterResult<(Option, Option)> + where + F: Clone, + Self: Sized, + dyn api::Connector: services::ConnectorIntegration, + { + Ok((None, None)) + } } macro_rules! default_imp_for_complete_authorize{ @@ -111,6 +126,53 @@ default_imp_for_complete_authorize!( connector::Zen ); +macro_rules! default_imp_for_create_customer{ + ($($path:ident::$connector:ident),*)=> { + $( + impl api::ConnectorCustomer for $path::$connector {} + impl + services::ConnectorIntegration< + api::CreateConnectorCustomer, + types::ConnectorCustomerData, + types::PaymentsResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_create_customer!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bluesnap, + connector::Braintree, + connector::Checkout, + connector::Coinbase, + connector::Cybersource, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nuvei, + connector::Opennode, + connector::Payeezy, + connector::Paypal, + connector::Payu, + connector::Rapyd, + connector::Shift4, + connector::Trustpay, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + macro_rules! default_imp_for_connector_redirect_response{ ($($path:ident::$connector:ident),*)=> { $( diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index b5432f0fb2..8e5093205f 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -34,6 +34,7 @@ impl state: &AppState, connector_id: &str, merchant_account: &storage::MerchantAccount, + customer: &Option, ) -> RouterResult< types::RouterData< api::Authorize, @@ -46,6 +47,7 @@ impl self.clone(), connector_id, merchant_account, + customer, ) .await } @@ -93,6 +95,15 @@ impl Feature for types::PaymentsAu ) -> RouterResult> { add_payment_method_token(state, connector, tokenization_action, self).await } + + async fn create_connector_customer<'a>( + &self, + state: &AppState, + connector: &api::ConnectorData, + customer: &Option, + ) -> RouterResult<(Option, Option)> { + create_connector_customer(state, connector, customer, self).await + } } impl types::PaymentsAuthorizeRouterData { @@ -367,6 +378,151 @@ impl mandate::MandateBehaviour for types::PaymentsAuthorizeData { } } +pub async fn update_connector_customer_in_customers( + connector: &api::ConnectorData, + connector_customer_map: Option>, + connector_cust_id: &Option, +) -> RouterResult> { + let mut connector_customer = match connector_customer_map { + Some(cc) => cc, + None => serde_json::Map::new(), + }; + connector_cust_id.clone().map(|cc| { + connector_customer.insert( + connector.connector_name.to_string(), + serde_json::Value::String(cc), + ) + }); + Ok(Some(storage::CustomerUpdate::ConnectorCustomer { + connector_customer: Some(serde_json::Value::Object(connector_customer)), + })) +} + +type CreateCustomerCheck = ( + bool, + Option, + Option>, +); +pub fn should_call_connector_create_customer( + state: &AppState, + connector: &api::ConnectorData, + customer: &Option, +) -> RouterResult { + let connector_name = connector.connector_name.to_string(); + //Check if create customer is required for the connector + let connector_customer_filter = state + .conf + .connector_customer + .connector_list + .contains(&connector.connector_name); + if connector_customer_filter { + match customer { + Some(customer) => match &customer.connector_customer { + Some(connector_customer) => { + let connector_customer_map: serde_json::Map = + connector_customer + .clone() + .parse_value("Map") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize Value to CustomerConnector")?; + let value = connector_customer_map.get(&connector_name); //Check if customer already created for this customer and for this connector + Ok(( + value.is_none(), + value.and_then(|val| val.as_str().map(|cust| cust.to_string())), + Some(connector_customer_map), + )) + } + None => Ok((true, None, None)), + }, + None => Ok((false, None, None)), + } + } else { + Ok((false, None, None)) + } +} + +pub async fn create_connector_customer( + state: &AppState, + connector: &api::ConnectorData, + customer: &Option, + router_data: &types::RouterData, +) -> RouterResult<(Option, Option)> { + let (is_eligible, connector_customer_id, connector_customer_map) = + should_call_connector_create_customer(state, connector, customer)?; + + if is_eligible { + let connector_integration: services::BoxedConnectorIntegration< + '_, + api::CreateConnectorCustomer, + types::ConnectorCustomerData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + let customer_response_data: Result = + Err(types::ErrorResponse::default()); + + let customer_request_data = + types::ConnectorCustomerData::try_from(router_data.request.to_owned())?; + + let customer_router_data = payments::helpers::router_data_type_conversion::< + _, + api::CreateConnectorCustomer, + _, + _, + _, + _, + >( + router_data.clone(), + customer_request_data, + customer_response_data, + ); + + let resp = services::execute_connector_processing_step( + state, + connector_integration, + &customer_router_data, + payments::CallConnectorAction::Trigger, + ) + .await + .map_err(|error| error.to_payment_failed_response())?; + + let connector_customer_id = match resp.response { + Ok(response) => match response { + types::PaymentsResponseData::ConnectorCustomerResponse { + connector_customer_id, + } => Some(connector_customer_id), + _ => None, + }, + Err(err) => { + logger::debug!(payment_method_tokenization_error=?err); + None + } + }; + let update_customer = update_connector_customer_in_customers( + connector, + connector_customer_map, + &connector_customer_id, + ) + .await?; + Ok((connector_customer_id, update_customer)) + } else { + Ok((connector_customer_id, None)) + } +} + +impl TryFrom for types::ConnectorCustomerData { + type Error = error_stack::Report; + + fn try_from(data: types::PaymentsAuthorizeData) -> Result { + Ok(Self { + email: data.email, + description: None, + phone: None, + name: None, + }) + } +} + pub async fn add_payment_method_token( state: &AppState, connector: &api::ConnectorData, diff --git a/crates/router/src/core/payments/flows/cancel_flow.rs b/crates/router/src/core/payments/flows/cancel_flow.rs index 276cc6b9bd..0f5e5ca281 100644 --- a/crates/router/src/core/payments/flows/cancel_flow.rs +++ b/crates/router/src/core/payments/flows/cancel_flow.rs @@ -20,12 +20,14 @@ impl ConstructFlowSpecificData, ) -> RouterResult { transformers::construct_payment_router_data::( state, self.clone(), connector_id, merchant_account, + customer, ) .await } diff --git a/crates/router/src/core/payments/flows/capture_flow.rs b/crates/router/src/core/payments/flows/capture_flow.rs index e192dc7fc6..7a4d9d53c2 100644 --- a/crates/router/src/core/payments/flows/capture_flow.rs +++ b/crates/router/src/core/payments/flows/capture_flow.rs @@ -21,12 +21,14 @@ impl state: &AppState, connector_id: &str, merchant_account: &storage::MerchantAccount, + customer: &Option, ) -> RouterResult { transformers::construct_payment_router_data::( state, self.clone(), connector_id, merchant_account, + customer, ) .await } diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index 0e5f360703..b8712d52f6 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -24,6 +24,7 @@ impl state: &AppState, connector_id: &str, merchant_account: &storage::MerchantAccount, + customer: &Option, ) -> RouterResult< types::RouterData< api::CompleteAuthorize, @@ -34,7 +35,13 @@ impl transformers::construct_payment_router_data::< api::CompleteAuthorize, types::CompleteAuthorizeData, - >(state, self.clone(), connector_id, merchant_account) + >( + state, + self.clone(), + connector_id, + merchant_account, + customer, + ) .await } } diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index 61127956c0..f4cfcc1e00 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -20,6 +20,7 @@ impl ConstructFlowSpecificData, ) -> RouterResult< types::RouterData, > { @@ -28,6 +29,7 @@ impl ConstructFlowSpecificData, ) -> RouterResult { transformers::construct_payment_router_data::( state, self.clone(), connector_id, merchant_account, + customer, ) .await } diff --git a/crates/router/src/core/payments/flows/verfiy_flow.rs b/crates/router/src/core/payments/flows/verfiy_flow.rs index 0f97ad77d9..2a6f38f144 100644 --- a/crates/router/src/core/payments/flows/verfiy_flow.rs +++ b/crates/router/src/core/payments/flows/verfiy_flow.rs @@ -21,12 +21,14 @@ impl ConstructFlowSpecificData, ) -> RouterResult { transformers::construct_payment_router_data::( state, self.clone(), connector_id, merchant_account, + customer, ) .await } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 40e17d1a01..37a414d38c 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1137,6 +1137,7 @@ pub fn generate_mandate( customer: &Option, payment_method_id: String, connector_mandate_id: Option, + network_txn_id: Option, ) -> Option { match (setup_mandate_details, customer) { (Some(data), Some(cus)) => { @@ -1153,6 +1154,7 @@ pub fn generate_mandate( .set_connector(connector) .set_mandate_status(storage_enums::MandateStatus::Active) .set_connector_mandate_id(connector_mandate_id) + .set_network_transaction_id(network_txn_id) .set_customer_ip_address( data.customer_acceptance .get_ip_address() @@ -1489,5 +1491,7 @@ pub fn router_data_type_conversion( session_token: router_data.session_token, reference_id: None, payment_method_token: router_data.payment_method_token, + customer_id: router_data.customer_id, + connector_customer: router_data.connector_customer, } } diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index daf1f1a9f1..b53e3307fb 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -139,6 +139,7 @@ pub trait UpdateTracker: Send { payment_data: D, customer: Option, storage_scheme: enums::MerchantStorageScheme, + updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, Req>, D)> where F: 'b + Send; diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 57c90ab363..f589b61745 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -145,6 +145,7 @@ impl GetTracker, api::PaymentsCancelRequest> card_cvc: None, creds_identifier, pm_token: None, + connector_customer_id: None, }, None, )), @@ -162,6 +163,7 @@ impl UpdateTracker, api::PaymentsCancelRequest> for mut payment_data: PaymentData, _customer: Option, storage_scheme: enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsCancelRequest>, PaymentData, diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 968971f41a..814839ef2e 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -155,6 +155,7 @@ impl GetTracker, api::PaymentsCaptu card_cvc: None, creds_identifier, pm_token: None, + connector_customer_id: None, }, None, )) @@ -173,6 +174,7 @@ impl UpdateTracker, api::PaymentsCaptureRe payment_data: payments::PaymentData, _customer: Option, _storage_scheme: enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsCaptureRequest>, payments::PaymentData, 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 1e382179ec..7d480e29bc 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -197,6 +197,7 @@ impl GetTracker, api::PaymentsRequest> for Co card_cvc: request.card_cvc.clone(), creds_identifier: None, pm_token: None, + connector_customer_id: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), @@ -286,6 +287,7 @@ impl UpdateTracker, api::PaymentsRequest> for Comple payment_data: PaymentData, _customer: Option, _storage_scheme: storage_enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 480eef30d2..7d711b3e61 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -216,6 +216,7 @@ impl GetTracker, api::PaymentsRequest> for Pa card_cvc: request.card_cvc.clone(), creds_identifier, pm_token: None, + connector_customer_id: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), @@ -305,6 +306,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen mut payment_data: PaymentData, customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, + updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> where F: 'b + Send, @@ -365,7 +367,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen payment_data.payment_intent.billing_address_id.clone(), ); - let customer_id = customer.map(|c| c.customer_id); + let customer_id = customer.clone().map(|c| c.customer_id); let return_url = payment_data.payment_intent.return_url.clone(); let setup_future_usage = payment_data.payment_intent.setup_future_usage; let business_label = Some(payment_data.payment_intent.business_label.clone()); @@ -391,6 +393,17 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + if let Some((updated_customer, customer)) = updated_customer.zip(customer) { + db.update_customer_by_customer_id_merchant_id( + customer.customer_id.to_owned(), + customer.merchant_id.to_owned(), + updated_customer, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update CustomerConnector in customer")?; + }; + Ok((Box::new(self), payment_data)) } } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 88ba6ffcab..f3f9236a9d 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -152,7 +152,24 @@ impl GetTracker, api::PaymentsRequest> for Pa .change_context(errors::ApiErrorResponse::MandateNotFound); Some(mandate.map(|mandate_obj| api_models::payments::MandateIds { mandate_id: mandate_obj.mandate_id, - connector_mandate_id: mandate_obj.connector_mandate_id, + mandate_reference_id: { + match ( + mandate_obj.network_transaction_id, + mandate_obj.connector_mandate_id, + ) { + (Some(network_tx_id), _) => { + Some(api_models::payments::MandateReferenceId::NetworkMandateId( + network_tx_id, + )) + } + (_, Some(connector_mandate_id)) => Some( + api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_mandate_id, + ), + ), + (_, _) => None, + } + }, })) }) .await @@ -207,6 +224,7 @@ impl GetTracker, api::PaymentsRequest> for Pa card_cvc: request.card_cvc.clone(), creds_identifier, pm_token: None, + connector_customer_id: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), @@ -287,6 +305,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen mut payment_data: PaymentData, _customer: Option, storage_scheme: enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> where F: 'b + Send, 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 b0bd32efae..b4d092db30 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -176,6 +176,7 @@ impl GetTracker, api::VerifyRequest> for Paym card_cvc: None, creds_identifier, pm_token: None, + connector_customer_id: None, }, Some(payments::CustomerDetails { customer_id: request.customer_id.clone(), @@ -198,6 +199,7 @@ impl UpdateTracker, api::VerifyRequest> for PaymentM mut payment_data: PaymentData, _customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::VerifyRequest>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index a4849b5dc8..6af82659fe 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -387,6 +387,7 @@ async fn payment_response_update_tracker( types::PaymentsResponseData::SessionResponse { .. } => (None, None), types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None), types::PaymentsResponseData::TokenizationResponse { .. } => (None, None), + types::PaymentsResponseData::ConnectorCustomerResponse { .. } => (None, None), types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => (None, None), }, }; diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 27b819ca1f..2a901eb604 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -175,6 +175,7 @@ impl GetTracker, api::PaymentsSessionRequest> card_cvc: None, creds_identifier, pm_token: None, + connector_customer_id: None, }, Some(customer_details), )) @@ -191,6 +192,7 @@ impl UpdateTracker, api::PaymentsSessionRequest> for mut payment_data: PaymentData, _customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsSessionRequest>, PaymentData, diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index f9723eff9e..c589280687 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -142,6 +142,7 @@ impl GetTracker, api::PaymentsStartRequest> f card_cvc: None, creds_identifier: None, pm_token: None, + connector_customer_id: None, }, Some(customer_details), )) @@ -158,6 +159,7 @@ impl UpdateTracker, api::PaymentsStartRequest> for P payment_data: PaymentData, _customer: Option, _storage_scheme: storage_enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsStartRequest>, PaymentData, diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 3a347ea686..ec25f5995e 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -116,6 +116,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen payment_data: PaymentData, _customer: Option, _storage_scheme: enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> where F: 'b + Send, @@ -133,6 +134,7 @@ impl UpdateTracker, api::PaymentsRetrieveRequest> fo payment_data: PaymentData, _customer: Option, _storage_scheme: enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsRetrieveRequest>, PaymentData, @@ -272,6 +274,7 @@ async fn get_tracker_for_sync< card_cvc: None, creds_identifier, pm_token: None, + connector_customer_id: None, }, None, )) diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 5e22592bc0..e84d5076b9 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -193,7 +193,24 @@ impl GetTracker, api::PaymentsRequest> for Pa .change_context(errors::ApiErrorResponse::MandateNotFound); Some(mandate.map(|mandate_obj| api_models::payments::MandateIds { mandate_id: mandate_obj.mandate_id, - connector_mandate_id: mandate_obj.connector_mandate_id, + mandate_reference_id: { + match ( + mandate_obj.network_transaction_id, + mandate_obj.connector_mandate_id, + ) { + (Some(network_tx_id), _) => { + Some(api_models::payments::MandateReferenceId::NetworkMandateId( + network_tx_id, + )) + } + (_, Some(connector_mandate_id)) => Some( + api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_mandate_id, + ), + ), + (_, _) => None, + } + }, })) }) .await @@ -268,6 +285,7 @@ impl GetTracker, api::PaymentsRequest> for Pa card_cvc: request.card_cvc.clone(), creds_identifier, pm_token: None, + connector_customer_id: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), @@ -348,6 +366,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen mut payment_data: PaymentData, customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, + _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index e629cbf2c3..ba8dcfd474 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -27,6 +27,7 @@ pub async fn construct_payment_router_data<'a, F, T>( payment_data: PaymentData, connector_id: &str, merchant_account: &storage::MerchantAccount, + customer: &Option, ) -> RouterResult> where T: TryFrom>, @@ -74,6 +75,7 @@ where redirection_data: None, mandate_reference: None, connector_metadata: None, + network_txn_id: None, }); let additional_data = PaymentAdditionalData { @@ -83,9 +85,12 @@ where state, }; + let customer_id = customer.to_owned().map(|customer| customer.customer_id); + router_data = types::RouterData { flow: PhantomData, merchant_id: merchant_account.merchant_id.clone(), + customer_id, connector: connector_id.to_owned(), payment_id: payment_data.payment_attempt.payment_id.clone(), attempt_id: payment_data.payment_attempt.attempt_id.clone(), @@ -108,6 +113,7 @@ where session_token: None, reference_id: None, payment_method_token: payment_data.pm_token, + connector_customer: payment_data.connector_customer_id, }; Ok(router_data) diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index f2f06f07e6..bed8baa772 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -63,6 +63,7 @@ pub async fn construct_refund_router_data<'a, F>( let router_data = types::RouterData { flow: PhantomData, merchant_id: merchant_account.merchant_id.clone(), + customer_id: payment_intent.customer_id.to_owned(), connector: connector_id.to_string(), payment_id: payment_attempt.payment_id.clone(), attempt_id: payment_attempt.attempt_id.clone(), @@ -96,6 +97,7 @@ pub async fn construct_refund_router_data<'a, F>( session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, }; Ok(router_data) @@ -279,6 +281,8 @@ pub async fn construct_accept_dispute_router_data<'a>( session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, + customer_id: None, }; Ok(router_data) } @@ -336,6 +340,8 @@ pub async fn construct_submit_evidence_router_data<'a>( session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, + customer_id: None, }; Ok(router_data) } @@ -398,6 +404,8 @@ pub async fn construct_upload_file_router_data<'a>( session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, + customer_id: None, }; Ok(router_data) } @@ -457,6 +465,8 @@ pub async fn construct_defend_dispute_router_data<'a>( session_token: None, reference_id: None, payment_method_token: None, + customer_id: None, + connector_customer: None, }; Ok(router_data) } diff --git a/crates/router/src/db/customers.rs b/crates/router/src/db/customers.rs index cbdd99e2fd..7e1519b6fa 100644 --- a/crates/router/src/db/customers.rs +++ b/crates/router/src/db/customers.rs @@ -184,6 +184,7 @@ impl CustomerInterface for MockDb { description: customer_data.description, created_at: common_utils::date_time::now(), metadata: customer_data.metadata, + connector_customer: customer_data.connector_customer, modified_at: common_utils::date_time::now(), }; customers.push(customer.clone()); diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 6ca53865e0..a799c9245b 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -39,6 +39,8 @@ pub type RefundExecuteRouterData = RouterData; pub type TokenizationRouterData = RouterData; +pub type ConnectorCustomerRouterData = + RouterData; pub type RefreshTokenRouterData = RouterData; @@ -61,6 +63,12 @@ pub type TokenizationResponseRouterData = ResponseRouterData< PaymentMethodTokenizationData, PaymentsResponseData, >; +pub type ConnectorCustomerResponseRouterData = ResponseRouterData< + api::CreateConnectorCustomer, + R, + ConnectorCustomerData, + PaymentsResponseData, +>; pub type RefundsResponseRouterData = ResponseRouterData; @@ -97,6 +105,12 @@ pub type TokenizationType = dyn services::ConnectorIntegration< PaymentsResponseData, >; +pub type ConnectorCustomerType = dyn services::ConnectorIntegration< + api::CreateConnectorCustomer, + ConnectorCustomerData, + PaymentsResponseData, +>; + pub type RefundExecuteType = dyn services::ConnectorIntegration; pub type RefundSyncType = @@ -143,6 +157,8 @@ pub type DefendDisputeRouterData = pub struct RouterData { pub flow: PhantomData, pub merchant_id: String, + pub customer_id: Option, + pub connector_customer: Option, pub connector: String, pub payment_id: String, pub attempt_id: String, @@ -214,6 +230,14 @@ pub struct AuthorizeSessionTokenData { pub amount: i64, } +#[derive(Debug, Clone)] +pub struct ConnectorCustomerData { + pub description: Option, + pub email: Option>, + pub phone: Option>, + pub name: Option, +} + #[derive(Debug, Clone)] pub struct PaymentMethodTokenizationData { pub payment_method_data: payments::PaymentMethodData, @@ -302,6 +326,7 @@ pub enum PaymentsResponseData { redirection_data: Option, mandate_reference: Option, connector_metadata: Option, + network_txn_id: Option, }, SessionResponse { session_token: api::SessionToken, @@ -317,6 +342,11 @@ pub enum PaymentsResponseData { TokenizationResponse { token: String, }, + + ConnectorCustomerResponse { + connector_customer_id: String, + }, + ThreeDSEnrollmentResponse { enrolled_v2: bool, related_transaction_id: Option, @@ -628,7 +658,9 @@ impl From<(&&mut RouterData, T2)> payment_id: data.payment_id.clone(), session_token: data.session_token.clone(), reference_id: data.reference_id.clone(), + customer_id: data.customer_id.clone(), payment_method_token: None, + connector_customer: data.connector_customer.clone(), } } } diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index e33269b8ae..4c02ab9a75 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -83,6 +83,9 @@ pub struct Session; #[derive(Debug, Clone)] pub struct PaymentMethodToken; +#[derive(Debug, Clone)] +pub struct CreateConnectorCustomer; + #[derive(Debug, Clone)] pub struct Verify; @@ -169,6 +172,15 @@ pub trait PaymentToken: { } +pub trait ConnectorCustomer: + api::ConnectorIntegration< + CreateConnectorCustomer, + types::ConnectorCustomerData, + types::PaymentsResponseData, +> +{ +} + pub trait Payment: api_types::ConnectorCommon + PaymentAuthorize @@ -179,6 +191,7 @@ pub trait Payment: + PreVerify + PaymentSession + PaymentToken + + ConnectorCustomer { } diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 4a944c2b7c..52169d9072 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -21,6 +21,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { types::RouterData { flow: PhantomData, merchant_id: String::from("aci"), + customer_id: Some(String::from("aci")), connector: "aci".to_string(), payment_id: uuid::Uuid::new_v4().to_string(), attempt_id: uuid::Uuid::new_v4().to_string(), @@ -71,6 +72,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, } } @@ -82,6 +84,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { types::RouterData { flow: PhantomData, merchant_id: String::from("aci"), + customer_id: Some(String::from("aci")), connector: "aci".to_string(), payment_id: uuid::Uuid::new_v4().to_string(), attempt_id: uuid::Uuid::new_v4().to_string(), @@ -111,6 +114,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, } } diff --git a/crates/router/tests/connectors/authorizedotnet.rs b/crates/router/tests/connectors/authorizedotnet.rs index eec99afd2a..aa5c236db4 100644 --- a/crates/router/tests/connectors/authorizedotnet.rs +++ b/crates/router/tests/connectors/authorizedotnet.rs @@ -21,6 +21,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { types::RouterData { flow: PhantomData, merchant_id: String::from("authorizedotnet"), + customer_id: Some(String::from("authorizedotnet")), connector: "authorizedotnet".to_string(), payment_id: uuid::Uuid::new_v4().to_string(), attempt_id: uuid::Uuid::new_v4().to_string(), @@ -71,6 +72,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, } } @@ -83,6 +85,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { flow: PhantomData, connector_meta_data: None, merchant_id: String::from("authorizedotnet"), + customer_id: Some(String::from("authorizedotnet")), connector: "authorizedotnet".to_string(), payment_id: uuid::Uuid::new_v4().to_string(), attempt_id: uuid::Uuid::new_v4().to_string(), @@ -110,6 +113,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, } } diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index d7019ba409..c5815819e9 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -364,6 +364,7 @@ pub trait ConnectorActions: Connector { RouterData { flow: PhantomData, merchant_id: self.get_name(), + customer_id: Some(self.get_name()), connector: self.get_name(), payment_id: uuid::Uuid::new_v4().to_string(), attempt_id: uuid::Uuid::new_v4().to_string(), @@ -394,6 +395,7 @@ pub trait ConnectorActions: Connector { session_token: None, reference_id: None, payment_method_token: None, + connector_customer: None, } } @@ -409,6 +411,7 @@ pub trait ConnectorActions: Connector { Ok(types::PaymentsResponseData::SessionTokenResponse { .. }) => None, Ok(types::PaymentsResponseData::TokenizationResponse { .. }) => None, Ok(types::PaymentsResponseData::TransactionUnresolvedResponse { .. }) => None, + Ok(types::PaymentsResponseData::ConnectorCustomerResponse { .. }) => None, Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. }) => None, Err(_) => None, } @@ -592,6 +595,7 @@ pub fn get_connector_transaction_id( Ok(types::PaymentsResponseData::SessionTokenResponse { .. }) => None, Ok(types::PaymentsResponseData::TokenizationResponse { .. }) => None, Ok(types::PaymentsResponseData::TransactionUnresolvedResponse { .. }) => None, + Ok(types::PaymentsResponseData::ConnectorCustomerResponse { .. }) => None, Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. }) => None, Err(_) => None, } @@ -606,6 +610,7 @@ pub fn get_connector_metadata( redirection_data: _, mandate_reference: _, connector_metadata, + network_txn_id: _, }) => connector_metadata, _ => None, } diff --git a/crates/storage_models/src/customers.rs b/crates/storage_models/src/customers.rs index 180ecf022e..6f9bb379ba 100644 --- a/crates/storage_models/src/customers.rs +++ b/crates/storage_models/src/customers.rs @@ -16,6 +16,7 @@ pub struct CustomerNew { pub description: Option, pub phone_country_code: Option, pub metadata: Option, + pub connector_customer: Option, } #[derive(Clone, Debug, Identifiable, Queryable)] @@ -31,6 +32,7 @@ pub struct Customer { pub description: Option, pub created_at: PrimitiveDateTime, pub metadata: Option, + pub connector_customer: Option, pub modified_at: PrimitiveDateTime, } @@ -43,6 +45,10 @@ pub enum CustomerUpdate { description: Option, phone_country_code: Option, metadata: Option, + connector_customer: Option, + }, + ConnectorCustomer { + connector_customer: Option, }, } @@ -55,6 +61,7 @@ pub struct CustomerUpdateInternal { description: Option, phone_country_code: Option, metadata: Option, + connector_customer: Option, modified_at: Option, } @@ -68,6 +75,7 @@ impl From for CustomerUpdateInternal { description, phone_country_code, metadata, + connector_customer, } => Self { name, email, @@ -75,8 +83,14 @@ impl From for CustomerUpdateInternal { description, phone_country_code, metadata, + connector_customer, modified_at: Some(common_utils::date_time::now()), }, + CustomerUpdate::ConnectorCustomer { connector_customer } => Self { + connector_customer, + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, } } } diff --git a/crates/storage_models/src/schema.rs b/crates/storage_models/src/schema.rs index d6723fba9f..9509216170 100644 --- a/crates/storage_models/src/schema.rs +++ b/crates/storage_models/src/schema.rs @@ -106,6 +106,7 @@ diesel::table! { description -> Nullable, created_at -> Timestamp, metadata -> Nullable, + connector_customer -> Nullable, modified_at -> Timestamp, } } diff --git a/migrations/2023-04-19-120503_update_customer_connector_customer/down.sql b/migrations/2023-04-19-120503_update_customer_connector_customer/down.sql new file mode 100644 index 0000000000..cfc9dcd340 --- /dev/null +++ b/migrations/2023-04-19-120503_update_customer_connector_customer/down.sql @@ -0,0 +1 @@ +ALTER TABLE customers DROP COLUMN connector_customer; \ No newline at end of file diff --git a/migrations/2023-04-19-120503_update_customer_connector_customer/up.sql b/migrations/2023-04-19-120503_update_customer_connector_customer/up.sql new file mode 100644 index 0000000000..cab5c6a076 --- /dev/null +++ b/migrations/2023-04-19-120503_update_customer_connector_customer/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE customers +ADD COLUMN connector_customer JSONB; \ No newline at end of file