From 4af8c9c15597276474861718fad7367e1cd9491b Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Wed, 27 Mar 2024 16:38:08 +0530 Subject: [PATCH 01/15] implement mandate for card and wallet in BOA --- .../connector/bankofamerica/transformers.rs | 490 +++++++++++++----- 1 file changed, 358 insertions(+), 132 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index b4b8ce87ec86..a88ed260a572 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -1,5 +1,6 @@ use api_models::payments; use base64::Engine; +use common_enums::FutureUsage; use common_utils::{ext_traits::ValueExt, pii}; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface, Secret}; @@ -10,7 +11,7 @@ use crate::{ connector::utils::{ self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - PaymentsPreProcessingData, PaymentsSyncRequestData, RouterData, + PaymentsPreProcessingData, PaymentsSyncRequestData, RecurringMandateData, RouterData, }, consts, core::errors, @@ -97,9 +98,55 @@ pub struct BankOfAmericaPaymentsRequest { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ProcessingInformation { + action_list: Option>, + action_token_types: Option>, + authorization_options: Option, + commerce_indicator: String, capture: Option, + capture_options: Option, payment_solution: Option, - commerce_indicator: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum BankOfAmericaActionsList { + TokenCreate, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum BankOfAmericaActionsTokenType { + PaymentInstrument, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaAuthorizationOptions { + initiator: Option, + merchant_intitiated_transaction: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaPaymentInitiator { + #[serde(rename = "type")] + initiator_type: Option, + credential_stored_on_file: Option, + stored_credential_used: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum BankOfAmericaPaymentInitiatorTypes { + Customer, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantInitiatedTransaction { + reason: Option, + //Required for recurring mandates payment + original_authorized_amount: Option, } #[derive(Debug, Serialize)] @@ -127,6 +174,11 @@ pub struct CaptureOptions { total_capture_count: u32, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BankOfAmericaPaymentInstrument { + id: Secret, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct CardPaymentInformation { @@ -165,6 +217,13 @@ pub enum PaymentInformation { GooglePay(GooglePayPaymentInformation), ApplePay(ApplePayPaymentInformation), ApplePayToken(ApplePayTokenPaymentInformation), + MandatePayment(MandatePaymentInformation), +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MandatePaymentInformation { + payment_instrument: BankOfAmericaPaymentInstrument, } #[derive(Debug, Serialize)] @@ -328,19 +387,77 @@ impl } impl - From<( + TryFrom<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, Option, Option, )> for ProcessingInformation { - fn from( + type Error = error_stack::Report; + + fn try_from( (item, solution, network): ( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, Option, Option, ), - ) -> Self { + ) -> Result { + let (action_list, action_token_types, authorization_options) = if item + .router_data + .request + .setup_future_usage + .map_or(false, |future_usage| { + matches!(future_usage, common_enums::FutureUsage::OffSession) + }) + && (item.router_data.request.customer_acceptance.is_some() + || item + .router_data + .request + .setup_mandate_details + .clone() + .map_or(false, |mandate_details| { + mandate_details.customer_acceptance.is_some() + })) { + ( + Some(vec![BankOfAmericaActionsList::TokenCreate]), + Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), + Some(BankOfAmericaAuthorizationOptions { + initiator: Some(BankOfAmericaPaymentInitiator { + initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, + }), + merchant_intitiated_transaction: None, + }), + ) + } else if item.router_data.request.connector_mandate_id().is_some() { + let original_amount = item + .router_data + .get_recurring_mandate_payment_data()? + .get_original_payment_amount()?; + let original_currency = item + .router_data + .get_recurring_mandate_payment_data()? + .get_original_payment_currency()?; + ( + None, + None, + Some(BankOfAmericaAuthorizationOptions { + initiator: None, + merchant_intitiated_transaction: Some(MerchantInitiatedTransaction { + reason: None, + original_authorized_amount: Some(utils::get_amount_as_string( + &types::api::CurrencyUnit::Base, + original_amount, + original_currency, + )?), + }), + }), + ) + } else { + (None, None, None) + }; + let commerce_indicator = match network { Some(card_network) => match card_network.to_lowercase().as_str() { "amex" => "aesk", @@ -352,14 +469,18 @@ impl None => "internet", } .to_string(); - Self { + Ok(Self { capture: Some(matches!( item.router_data.request.capture_method, Some(enums::CaptureMethod::Automatic) | None )), payment_solution: solution.map(String::from), + action_list, + action_token_types, + authorization_options, + capture_options: None, commerce_indicator, - } + }) } } @@ -377,12 +498,38 @@ impl &BankOfAmericaConsumerAuthValidateResponse, ), ) -> Self { + let (action_list, action_token_types, authorization_options) = if item + .router_data + .request + .setup_future_usage + .map_or(false, |future_usage| { + matches!(future_usage, FutureUsage::OffSession) + }) { + ( + Some(vec![BankOfAmericaActionsList::TokenCreate]), + Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), + Some(BankOfAmericaAuthorizationOptions { + initiator: Some(BankOfAmericaPaymentInitiator { + initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, + }), + merchant_intitiated_transaction: None, + }), + ) + } else { + (None, None, None) + }; Self { capture: Some(matches!( item.router_data.request.capture_method, Some(enums::CaptureMethod::Automatic) | None )), payment_solution: solution.map(String::from), + action_list, + action_token_types, + authorization_options, + capture_options: None, commerce_indicator: three_ds_data .indicator .to_owned() @@ -440,7 +587,6 @@ pub struct ClientReferenceInformation { pub struct ClientProcessorInformation { avs: Option, } - #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientRiskInformation { @@ -566,7 +712,7 @@ impl card_type, }, }); - let processing_information = ProcessingInformation::from((item, None, None)); + let processing_information = ProcessingInformation::try_from((item, None, None))?; let client_reference_information = ClientReferenceInformation::from(item); let merchant_defined_information = item.router_data.request.metadata.clone().map(|metadata| { @@ -602,11 +748,11 @@ impl let email = item.router_data.request.get_email()?; let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; let order_information = OrderInformationWithBill::from((item, bill_to)); - let processing_information = ProcessingInformation::from(( + let processing_information = ProcessingInformation::try_from(( item, Some(PaymentSolution::ApplePay), Some(apple_pay_wallet_data.payment_method.network.clone()), - )); + ))?; let client_reference_information = ClientReferenceInformation::from(item); let expiration_month = apple_pay_data.get_expiry_month()?; let expiration_year = apple_pay_data.get_four_digit_expiry_year()?; @@ -674,7 +820,7 @@ impl }, }); let processing_information = - ProcessingInformation::from((item, Some(PaymentSolution::GooglePay), None)); + ProcessingInformation::try_from((item, Some(PaymentSolution::GooglePay), None))?; let client_reference_information = ClientReferenceInformation::from(item); let merchant_defined_information = item.router_data.request.metadata.clone().map(|metadata| { @@ -699,126 +845,151 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> fn try_from( item: &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), - payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - payments::WalletData::ApplePay(apple_pay_data) => { - match item.router_data.payment_method_token.clone() { - Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - Self::try_from((item, decrypt_data, apple_pay_data)) - } - types::PaymentMethodToken::Token(_) => { - Err(unimplemented_payment_method!( - "Apple Pay", - "Manual", - "Bank Of America" - ))? - } - }, - None => { - let email = item.router_data.request.get_email()?; - let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; - let order_information = OrderInformationWithBill::from((item, bill_to)); - let processing_information = ProcessingInformation::from(( - item, - Some(PaymentSolution::ApplePay), - Some(apple_pay_data.payment_method.network.clone()), - )); - let client_reference_information = - ClientReferenceInformation::from(item); - let payment_information = PaymentInformation::ApplePayToken( - ApplePayTokenPaymentInformation { - fluid_data: FluidData { - value: Secret::from(apple_pay_data.payment_data), - }, - tokenized_card: ApplePayTokenizedCard { - transaction_type: TransactionType::ApplePay, - }, + match item.router_data.request.connector_mandate_id() { + Some(connector_mandate_id) => Self::try_from((item, connector_mandate_id)), + None => { + match item.router_data.request.payment_method_data.clone() { + payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + payments::WalletData::ApplePay(apple_pay_data) => { + match item.router_data.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + Self::try_from((item, decrypt_data, apple_pay_data)) + } + types::PaymentMethodToken::Token(_) => { + Err(unimplemented_payment_method!( + "Apple Pay", + "Manual", + "Bank Of America" + ))? + } }, - ); - let merchant_defined_information = - item.router_data.request.metadata.clone().map(|metadata| { - Vec::::foreign_from( - metadata.peek().to_owned(), - ) - }); - let ucaf_collection_indicator = match apple_pay_data - .payment_method - .network - .to_lowercase() - .as_str() - { - "mastercard" => Some("2".to_string()), - _ => None, - }; - Ok(Self { - processing_information, - payment_information, - order_information, - merchant_defined_information, - client_reference_information, - consumer_authentication_information: Some( - BankOfAmericaConsumerAuthInformation { - ucaf_collection_indicator, - cavv: None, - ucaf_authentication_data: None, - xid: None, - directory_server_transaction_id: None, - specification_version: None, - }, + None => { + let email = item.router_data.request.get_email()?; + let bill_to = + build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = + OrderInformationWithBill::from((item, bill_to)); + let processing_information = + ProcessingInformation::try_from(( + item, + Some(PaymentSolution::ApplePay), + Some(apple_pay_data.payment_method.network.clone()), + ))?; + let client_reference_information = + ClientReferenceInformation::from(item); + let payment_information = PaymentInformation::ApplePayToken( + ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }, + ); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from( + metadata.peek().to_owned(), + ) + }); + let ucaf_collection_indicator = match apple_pay_data + .payment_method + .network + .to_lowercase() + .as_str() + { + "mastercard" => Some("2".to_string()), + _ => None, + }; + Ok(Self { + processing_information, + payment_information, + order_information, + merchant_defined_information, + client_reference_information, + consumer_authentication_information: Some( + BankOfAmericaConsumerAuthInformation { + ucaf_collection_indicator, + cavv: None, + ucaf_authentication_data: None, + xid: None, + directory_server_transaction_id: None, + specification_version: None, + }, + ), + }) + } + } + } + payments::WalletData::GooglePay(google_pay_data) => { + Self::try_from((item, google_pay_data)) + } + + payments::WalletData::AliPayQr(_) + | payments::WalletData::AliPayRedirect(_) + | payments::WalletData::AliPayHkRedirect(_) + | payments::WalletData::MomoRedirect(_) + | payments::WalletData::KakaoPayRedirect(_) + | payments::WalletData::GoPayRedirect(_) + | payments::WalletData::GcashRedirect(_) + | payments::WalletData::ApplePayRedirect(_) + | payments::WalletData::ApplePayThirdPartySdk(_) + | payments::WalletData::DanaRedirect {} + | payments::WalletData::GooglePayRedirect(_) + | payments::WalletData::GooglePayThirdPartySdk(_) + | payments::WalletData::MbWayRedirect(_) + | payments::WalletData::MobilePayRedirect(_) + | payments::WalletData::PaypalRedirect(_) + | payments::WalletData::PaypalSdk(_) + | payments::WalletData::SamsungPay(_) + | payments::WalletData::TwintRedirect {} + | payments::WalletData::VippsRedirect {} + | payments::WalletData::TouchNGoRedirect(_) + | payments::WalletData::WeChatPayRedirect(_) + | payments::WalletData::WeChatPayQr(_) + | payments::WalletData::CashappQr(_) + | payments::WalletData::SwishQr(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message( + "Bank of America", ), - }) + ) + .into()) } + }, + // If connector_mandate_id is present MandatePayment will be the PMD, the case will be handled in the first `if` clause. + // This is a fallback implementation in the event of catastrophe. + payments::PaymentMethodData::MandatePayment => { + let connector_mandate_id = + item.router_data.request.connector_mandate_id().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_id", + }, + )?; + Self::try_from((item, connector_mandate_id)) + } + payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message( + "Bank of America", + ), + ) + .into()) } } - payments::WalletData::GooglePay(google_pay_data) => { - Self::try_from((item, google_pay_data)) - } - payments::WalletData::AliPayQr(_) - | payments::WalletData::AliPayRedirect(_) - | payments::WalletData::AliPayHkRedirect(_) - | payments::WalletData::MomoRedirect(_) - | payments::WalletData::KakaoPayRedirect(_) - | payments::WalletData::GoPayRedirect(_) - | payments::WalletData::GcashRedirect(_) - | payments::WalletData::ApplePayRedirect(_) - | payments::WalletData::ApplePayThirdPartySdk(_) - | payments::WalletData::DanaRedirect {} - | payments::WalletData::GooglePayRedirect(_) - | payments::WalletData::GooglePayThirdPartySdk(_) - | payments::WalletData::MbWayRedirect(_) - | payments::WalletData::MobilePayRedirect(_) - | payments::WalletData::PaypalRedirect(_) - | payments::WalletData::PaypalSdk(_) - | payments::WalletData::SamsungPay(_) - | payments::WalletData::TwintRedirect {} - | payments::WalletData::VippsRedirect {} - | payments::WalletData::TouchNGoRedirect(_) - | payments::WalletData::WeChatPayRedirect(_) - | payments::WalletData::WeChatPayQr(_) - | payments::WalletData::CashappQr(_) - | payments::WalletData::SwishQr(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Bank of America"), - ) - .into()), - }, - payments::PaymentMethodData::CardRedirect(_) - | payments::PaymentMethodData::PayLater(_) - | payments::PaymentMethodData::BankRedirect(_) - | payments::PaymentMethodData::BankDebit(_) - | payments::PaymentMethodData::BankTransfer(_) - | payments::PaymentMethodData::Crypto(_) - | payments::PaymentMethodData::MandatePayment - | payments::PaymentMethodData::Reward - | payments::PaymentMethodData::Upi(_) - | payments::PaymentMethodData::Voucher(_) - | payments::PaymentMethodData::GiftCard(_) - | payments::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Bank of America"), - ) - .into()) } } } @@ -860,14 +1031,14 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> client_reference_information, }) } - payments::PaymentMethodData::Wallet(_) + payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Wallet(_) | payments::PaymentMethodData::CardRedirect(_) | payments::PaymentMethodData::PayLater(_) | payments::PaymentMethodData::BankRedirect(_) | payments::PaymentMethodData::BankDebit(_) | payments::PaymentMethodData::BankTransfer(_) | payments::PaymentMethodData::Crypto(_) - | payments::PaymentMethodData::MandatePayment | payments::PaymentMethodData::Reward | payments::PaymentMethodData::Upi(_) | payments::PaymentMethodData::Voucher(_) @@ -882,6 +1053,44 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> } } +impl + TryFrom<( + &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + String, + )> for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, connector_mandate_id): ( + &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + String, + ), + ) -> Result { + let processing_information = ProcessingInformation::try_from((item, None, None))?; + let payment_instrument = BankOfAmericaPaymentInstrument { + id: connector_mandate_id.into(), + }; + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + let payment_information = + PaymentInformation::MandatePayment(MandatePaymentInformation { payment_instrument }); + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, + }) + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum BankofamericaPaymentStatus { @@ -983,9 +1192,16 @@ pub struct BankOfAmericaClientReferenceResponse { client_reference_information: ClientReferenceInformation, processor_information: Option, risk_information: Option, + token_information: Option, error_information: Option, } +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaTokenInformation { + payment_instrument: BankOfAmericaPaymentInstrument, +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct BankOfAmericaErrorInformationResponse { @@ -1077,10 +1293,19 @@ fn get_payment_response( let error_response = get_error_response_if_failure((info_response, status, http_code)); match error_response { Some(error) => Err(error), - None => Ok(types::PaymentsResponseData::TransactionResponse { + None => { + let mandate_reference = + info_response + .token_information + .clone() + .map(|token_info| types::MandateReference { + connector_mandate_id: Some(token_info.payment_instrument.id.expose()), + payment_method_id: None, + }); + Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), redirection_data: None, - mandate_reference: None, + mandate_reference, connector_metadata: info_response .processor_information .as_ref() @@ -1094,7 +1319,8 @@ fn get_payment_response( .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed: None, - }), + }) + } } } From 94d2bfdf12547e866cc6c51f7de6d1e46fbd6364 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Wed, 27 Mar 2024 18:19:28 +0530 Subject: [PATCH 02/15] feat(bankofamerica): implement setup mandate flow --- crates/router/src/connector/bankofamerica.rs | 65 +++- .../connector/bankofamerica/transformers.rs | 279 +++++++++++++++++- 2 files changed, 336 insertions(+), 8 deletions(-) diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index aac441201eb0..0b4ec5736c3d 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -290,19 +290,72 @@ impl types::PaymentsResponseData, > for Bankofamerica { + fn get_headers( + &self, + req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) + } + fn get_request_body( + &self, + req: &types::SetupMandateRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = bankofamerica::BankOfAmericaZeroMandateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( &self, - _req: &types::RouterData< + req: &types::RouterData< api::SetupMandate, types::SetupMandateRequestData, types::PaymentsResponseData, >, - _connectors: &settings::Connectors, + connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - Err(errors::ConnectorError::NotImplemented( - "Setup Mandate flow for Bankofamerica".to_string(), - ) - .into()) + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::SetupMandateType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::SetupMandateType::get_headers(self, req, connectors)?) + .set_body(types::SetupMandateType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::SetupMandateRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaSetupMandatesResponse = res + .response + .parse_struct("BankOfAmericaSetupMandatesResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) } } diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index a88ed260a572..c4f06aff3875 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -11,7 +11,8 @@ use crate::{ connector::utils::{ self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - PaymentsPreProcessingData, PaymentsSyncRequestData, RecurringMandateData, RouterData, + PaymentsPreProcessingData, PaymentsSetupMandateRequestData, PaymentsSyncRequestData, + RecurringMandateData, RouterData, }, consts, core::errors, @@ -280,6 +281,273 @@ pub struct BillTo { email: pii::Email, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaZeroMandateRequest { + processing_information: ProcessingInformation, + payment_information: PaymentInformation, + order_information: OrderInformationWithBill, + client_reference_information: ClientReferenceInformation, +} + +impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest { + type Error = error_stack::Report; + fn try_from(item: &types::SetupMandateRouterData) -> Result { + let email = item.request.get_email()?; + let bill_to = build_bill_to(item.get_billing()?, email)?; + + let order_information = OrderInformationWithBill { + amount_details: Amount { + total_amount: "0".to_string(), + currency: item.request.currency, + }, + bill_to, + }; + let (action_list, action_token_types, authorization_options) = ( + Some(vec![BankOfAmericaActionsList::TokenCreate]), + Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), + Some(BankOfAmericaAuthorizationOptions { + initiator: Some(BankOfAmericaPaymentInitiator { + initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, + }), + merchant_intitiated_transaction: None, + }), + ); + + let client_reference_information = ClientReferenceInformation { + code: Some(item.connector_request_reference_id.clone()), + }; + + let (payment_information, solution) = match item.request.payment_method_data.clone() { + api::PaymentMethodData::Card(ccard) => { + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + ( + PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }), + None, + ) + } + + api::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + payments::WalletData::ApplePay(apple_pay_data) => { + match item.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + let expiration_month = decrypt_data.get_expiry_month()?; + let expiration_year = decrypt_data.get_four_digit_expiry_year()?; + ( + PaymentInformation::ApplePay(ApplePayPaymentInformation { + tokenized_card: TokenizedCard { + number: decrypt_data.application_primary_account_number, + cryptogram: decrypt_data + .payment_data + .online_payment_cryptogram, + transaction_type: TransactionType::ApplePay, + expiration_year, + expiration_month, + }, + }), + Some(PaymentSolution::ApplePay), + ) + } + types::PaymentMethodToken::Token(_) => { + Err(unimplemented_payment_method!( + "Apple Pay", + "Manual", + "Bank Of America" + ))? + } + }, + None => ( + PaymentInformation::ApplePayToken(ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }), + Some(PaymentSolution::ApplePay), + ), + } + } + payments::WalletData::GooglePay(google_pay_data) => ( + PaymentInformation::GooglePay(GooglePayPaymentInformation { + fluid_data: FluidData { + value: Secret::from( + consts::BASE64_ENGINE + .encode(google_pay_data.tokenization_data.token), + ), + }, + }), + Some(PaymentSolution::GooglePay), + ), + payments::WalletData::AliPayQr(_) + | payments::WalletData::AliPayRedirect(_) + | payments::WalletData::AliPayHkRedirect(_) + | payments::WalletData::MomoRedirect(_) + | payments::WalletData::KakaoPayRedirect(_) + | payments::WalletData::GoPayRedirect(_) + | payments::WalletData::GcashRedirect(_) + | payments::WalletData::ApplePayRedirect(_) + | payments::WalletData::ApplePayThirdPartySdk(_) + | payments::WalletData::DanaRedirect {} + | payments::WalletData::GooglePayRedirect(_) + | payments::WalletData::GooglePayThirdPartySdk(_) + | payments::WalletData::MbWayRedirect(_) + | payments::WalletData::MobilePayRedirect(_) + | payments::WalletData::PaypalRedirect(_) + | payments::WalletData::PaypalSdk(_) + | payments::WalletData::SamsungPay(_) + | payments::WalletData::TwintRedirect {} + | payments::WalletData::VippsRedirect {} + | payments::WalletData::TouchNGoRedirect(_) + | payments::WalletData::WeChatPayRedirect(_) + | payments::WalletData::WeChatPayQr(_) + | payments::WalletData::CashappQr(_) + | payments::WalletData::SwishQr(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + ))?, + }, + payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + ))? + } + }; + + let processing_information = ProcessingInformation { + capture: Some(false), + capture_options: None, + action_list, + action_token_types, + authorization_options, + commerce_indicator: String::from("internet"), + payment_solution: solution.map(String::from), + }; + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + }) + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BankOfAmericaSetupMandatesResponse, + T, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BankOfAmericaSetupMandatesResponse, + T, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BankOfAmericaSetupMandatesResponse::ClientReferenceInformation(info_response) => { + let mandate_reference = info_response.token_information.clone().map(|token_info| { + types::MandateReference { + connector_mandate_id: Some(token_info.payment_instrument.id.expose()), + payment_method_id: None, + } + }); + let mut mandate_status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), false)); + if matches!(mandate_status, enums::AttemptStatus::Authorized) { + //In case of zero auth mandates we want to make the payment reach the terminal status so we are converting the authorized status to charged as well. + mandate_status = enums::AttemptStatus::Charged + } + let error_response = + get_error_response_if_failure((&info_response, mandate_status, item.http_code)); + + Ok(Self { + status: mandate_status, + response: match error_response { + Some(error) => Err(error), + None => Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + info_response.id.clone(), + ), + redirection_data: None, + mandate_reference, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .clone() + .unwrap_or(info_response.id), + ), + incremental_authorization_allowed: Some( + mandate_status == enums::AttemptStatus::Authorized, + ), + }), + }, + ..item.data + }) + } + BankOfAmericaSetupMandatesResponse::ErrorInformation(ref error_response) => { + let error_reason = error_response + .error_information + .message + .to_owned() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason.to_owned(); + let response = Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }); + Ok(Self { + response, + status: enums::AttemptStatus::Failure, + ..item.data + }) + } + } + } +} + // for bankofamerica each item in Billing is mandatory fn build_bill_to( address_details: &payments::Address, @@ -1045,7 +1313,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> | payments::PaymentMethodData::GiftCard(_) | payments::PaymentMethodData::CardToken(_) => { Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + utils::get_unimplemented_payment_method_error_message("Bank Of America"), ) .into()) } @@ -1184,6 +1452,13 @@ pub enum BankOfAmericaPaymentsResponse { ErrorInformation(BankOfAmericaErrorInformationResponse), } +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum BankOfAmericaSetupMandatesResponse { + ClientReferenceInformation(BankOfAmericaClientReferenceResponse), + ErrorInformation(BankOfAmericaErrorInformationResponse), +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct BankOfAmericaClientReferenceResponse { From 509bba3aa8ca2dba9a4be8207b7bf1b52971e038 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Thu, 28 Mar 2024 15:54:20 +0530 Subject: [PATCH 03/15] add 3ds to setup mandate flow --- crates/router/src/connector/bankofamerica.rs | 101 +++++++++++++++--- .../connector/bankofamerica/transformers.rs | 38 ++++--- crates/router/src/connector/utils.rs | 4 + 3 files changed, 115 insertions(+), 28 deletions(-) diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index 0b4ec5736c3d..87e6c56a9610 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -12,7 +12,7 @@ use time::OffsetDateTime; use transformers as bankofamerica; use url::Url; -use super::utils::{PaymentsAuthorizeRequestData, RouterData}; +use super::utils::{PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, RouterData}; use crate::{ configs::settings, connector::{utils as connector_utils, utils::RefundsRequestData}, @@ -302,18 +302,33 @@ impl } fn get_url( &self, - _req: &types::SetupMandateRouterData, + req: &types::SetupMandateRouterData, connectors: &settings::Connectors, ) -> CustomResult { - Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) + if req.is_three_ds() && req.request.is_card() { + Ok(format!( + "{}risk/v1/authentication-setups", + self.base_url(connectors) + )) + } else { + Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) + } } fn get_request_body( &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = bankofamerica::BankOfAmericaZeroMandateRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + if req.is_three_ds() && req.request.is_card() { + let connector_req = bankofamerica::BankOfAmericaAuthSetupRequest::try_from(( + &req.request.payment_method_data, + req.connector_request_reference_id.clone(), + ))?; + Ok(RequestContent::Json(Box::new(connector_req))) + } else { + let connector_req = bankofamerica::BankOfAmericaZeroMandateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } } fn build_request( @@ -343,18 +358,72 @@ impl event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: bankofamerica::BankOfAmericaSetupMandatesResponse = res + if data.is_three_ds() && data.request.is_card() { + let response: bankofamerica::BankOfAmericaAuthSetupResponse = res + .response + .parse_struct("Bankofamerica AuthSetupResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } else { + let response: bankofamerica::BankOfAmericaSetupMandatesResponse = res + .response + .parse_struct("BankOfAmericaSetupMandatesResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaServerErrorResponse = res .response - .parse_struct("BankOfAmericaSetupMandatesResponse") + .parse_struct("BankOfAmericaServerErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); + event_builder.map(|event| event.set_response_body(&response)); + router_env::logger::info!(error_response=?response); - types::RouterData::try_from(types::ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, + let attempt_status = match response.reason { + Some(reason) => match reason { + transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure), + transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None, + }, + None => None, + }; + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status, + connector_transaction_id: None, }) } } @@ -512,8 +581,10 @@ impl ConnectorIntegration> - for BankOfAmericaAuthSetupRequest -{ +impl TryFrom<(&payments::PaymentMethodData, String)> for BankOfAmericaAuthSetupRequest { type Error = error_stack::Report; fn try_from( - item: &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + (payment_method_data, connector_request_reference_id): ( + &payments::PaymentMethodData, + String, + ), ) -> Result { - match item.router_data.request.payment_method_data.clone() { + match payment_method_data.clone() { payments::PaymentMethodData::Card(ccard) => { let card_issuer = ccard.get_card_issuer(); let card_type = match card_issuer { @@ -1293,7 +1302,10 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> card_type, }, }); - let client_reference_information = ClientReferenceInformation::from(item); + let client_reference_information = ClientReferenceInformation { + code: Some(connector_request_reference_id), + }; + Ok(Self { payment_information, client_reference_information, @@ -1599,22 +1611,22 @@ fn get_payment_response( } } -impl +impl TryFrom< types::ResponseRouterData< F, BankOfAmericaAuthSetupResponse, - types::PaymentsAuthorizeData, + T, types::PaymentsResponseData, >, - > for types::RouterData + > for types::RouterData { type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, BankOfAmericaAuthSetupResponse, - types::PaymentsAuthorizeData, + T, types::PaymentsResponseData, >, ) -> Result { diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index a41c05f19be0..9a7149e6ac50 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -364,6 +364,7 @@ impl RevokeMandateRequestData for types::MandateRevokeRequestData { pub trait PaymentsSetupMandateRequestData { fn get_browser_info(&self) -> Result; fn get_email(&self) -> Result; + fn is_card(&self) -> bool; } impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { @@ -375,6 +376,9 @@ impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { fn get_email(&self) -> Result { self.email.clone().ok_or_else(missing_field_err("email")) } + fn is_card(&self) -> bool { + matches!(self.payment_method_data, api::PaymentMethodData::Card(_)) + } } pub trait PaymentsAuthorizeRequestData { fn is_auto_capture(&self) -> Result; From ba8ee8b463ca66d37ed0118836678bb76efa4b3b Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Thu, 28 Mar 2024 16:37:45 +0530 Subject: [PATCH 04/15] add mandates configs --- config/config.example.toml | 5 +++-- config/deployments/integration_test.toml | 8 ++++---- config/deployments/production.toml | 8 ++++---- config/deployments/sandbox.toml | 8 ++++---- config/development.toml | 8 ++++---- config/docker_compose.toml | 8 ++++---- loadtest/config/development.toml | 8 ++++---- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index 81cd78f72445..03ea94ba489c 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -380,7 +380,7 @@ slack_invite_url = "https://www.example.com/" # Slack invite url for hyperswit discord_invite_url = "https://www.example.com/" # Discord invite url for hyperswitch [mandates.supported_payment_methods] -card.credit = { connector_list = "stripe,adyen,cybersource" } # Mandate supported payment method type and connector for card +card.credit = { connector_list = "stripe,adyen,cybersource,bankofamerica"} # Mandate supported payment method type and connector for card wallet.paypal = { connector_list = "adyen" } # Mandate supported payment method type and connector for wallets pay_later.klarna = { connector_list = "adyen" } # Mandate supported payment method type and connector for pay_later bank_debit.ach = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit @@ -388,7 +388,8 @@ bank_debit.becs = { connector_list = "gocardless" } # Mandat bank_debit.sepa = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit bank_redirect.ideal = { connector_list = "stripe,adyen,globalpay" } # Mandate supported payment method type and connector for bank_redirect bank_redirect.sofort = { connector_list = "stripe,adyen,globalpay" } -wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon" } +wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } +wallet.google_pay = { connector_list = "bankofamerica"} bank_redirect.giropay = { connector_list = "adyen,globalpay" } diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 6d4cc21c3a35..7188690fce54 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -112,11 +112,11 @@ connectors_with_delayed_session_response = "trustpay,payme" bank_debit.ach.connector_list = "gocardless" bank_debit.becs.connector_list = "gocardless" bank_debit.sepa.connector_list = "gocardless" -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" pay_later.klarna.connector_list = "adyen" -wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon" -wallet.google_pay.connector_list = "stripe,adyen,cybersource" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" +wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" wallet.paypal.connector_list = "adyen" bank_redirect.ideal.connector_list = "stripe,adyen,globalpay" bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 7c8105795b87..219ebc40b036 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -112,11 +112,11 @@ enabled = false bank_debit.ach.connector_list = "gocardless" bank_debit.becs.connector_list = "gocardless" bank_debit.sepa.connector_list = "gocardless" -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" pay_later.klarna.connector_list = "adyen" -wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon" -wallet.google_pay.connector_list = "stripe,adyen,cybersource" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" +wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" wallet.paypal.connector_list = "adyen" bank_redirect.ideal.connector_list = "stripe,adyen,globalpay" bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index faf329db74cc..75927782f7a3 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -112,11 +112,11 @@ enabled = true bank_debit.ach.connector_list = "gocardless" bank_debit.becs.connector_list = "gocardless" bank_debit.sepa.connector_list = "gocardless" -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" pay_later.klarna.connector_list = "adyen" -wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon" -wallet.google_pay.connector_list = "stripe,adyen,cybersource" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" +wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" wallet.paypal.connector_list = "adyen" bank_redirect.ideal.connector_list = "stripe,adyen,globalpay" bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" diff --git a/config/development.toml b/config/development.toml index 66ed76c8f3cd..149ca6e0a833 100644 --- a/config/development.toml +++ b/config/development.toml @@ -491,11 +491,11 @@ connectors_with_webhook_source_verification_call = "paypal" [mandates.supported_payment_methods] pay_later.klarna = { connector_list = "adyen" } -wallet.google_pay = { connector_list = "stripe,adyen,cybersource" } -wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon" } +wallet.google_pay = { connector_list = "stripe,adyen,cybersource,bankofamerica" } +wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } wallet.paypal = { connector_list = "adyen" } -card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } -card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } +card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } +card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } bank_debit.ach = { connector_list = "gocardless" } bank_debit.becs = { connector_list = "gocardless" } bank_debit.sepa = { connector_list = "gocardless" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 7677fc4582d9..338c33862c5a 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -361,11 +361,11 @@ adyen = { banks = "aib,bank_of_scotland,danske_bank,first_direct,first_trust,hal [mandates.supported_payment_methods] pay_later.klarna = { connector_list = "adyen" } -wallet.google_pay = { connector_list = "stripe,adyen" } -wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon" } +wallet.google_pay = { connector_list = "stripe,adyen,bankofamerica" } +wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } wallet.paypal = { connector_list = "adyen" } -card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } -card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } +card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } +card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } bank_debit.ach = { connector_list = "gocardless" } bank_debit.becs = { connector_list = "gocardless" } bank_debit.sepa = { connector_list = "gocardless" } diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 6dfd921a2516..81709a46c769 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -250,11 +250,11 @@ supported_connectors = "braintree" [mandates.supported_payment_methods] pay_later.klarna = {connector_list = "adyen"} -wallet.google_pay = {connector_list = "stripe,adyen"} -wallet.apple_pay = {connector_list = "stripe,adyen"} +wallet.google_pay = {connector_list = "stripe,adyen,bankofamerica"} +wallet.apple_pay = {connector_list = "stripe,adyen,bankofamerica"} wallet.paypal = {connector_list = "adyen"} -card.credit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} -card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} +card.credit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica"} +card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica"} bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} From ec566a9c2a81faa74c2b0d0acb8bb66429d4dcf3 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Mon, 1 Apr 2024 14:24:21 +0530 Subject: [PATCH 05/15] refactor(boa): resolve comments --- .../connector/bankofamerica/transformers.rs | 93 +++++++++++++++---- crates/router/src/connector/utils.rs | 14 +++ 2 files changed, 89 insertions(+), 18 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 9dc5d4f5666c..8742c23880fc 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -1,6 +1,5 @@ use api_models::payments; use base64::Engine; -use common_enums::FutureUsage; use common_utils::{ext_traits::ValueExt, pii}; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface, Secret}; @@ -288,6 +287,10 @@ pub struct BankOfAmericaZeroMandateRequest { payment_information: PaymentInformation, order_information: OrderInformationWithBill, client_reference_information: ClientReferenceInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + consumer_authentication_information: Option, } impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest { @@ -320,7 +323,16 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest code: Some(item.connector_request_reference_id.clone()), }; - let (payment_information, solution) = match item.request.payment_method_data.clone() { + let merchant_defined_information = item.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + + let ( + payment_information, + solution, + commerce_indicator, + consumer_authentication_information, + ) = match item.request.payment_method_data.clone() { api::PaymentMethodData::Card(ccard) => { let card_issuer = ccard.get_card_issuer(); let card_type = match card_issuer { @@ -338,12 +350,14 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest }, }), None, + get_commerce_indicator(None), + None, ) } api::PaymentMethodData::Wallet(wallet_data) => match wallet_data { payments::WalletData::ApplePay(apple_pay_data) => { - match item.payment_method_token.clone() { + let (payment_information, solution) = match item.payment_method_token.clone() { Some(payment_method_token) => match payment_method_token { types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { let expiration_month = decrypt_data.get_expiry_month()?; @@ -382,7 +396,31 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest }), Some(PaymentSolution::ApplePay), ), - } + }; + ( + payment_information, + solution, + get_commerce_indicator(Some(apple_pay_data.payment_method.network.clone())), + { + let ucaf_collection_indicator = match apple_pay_data + .payment_method + .network + .to_lowercase() + .as_str() + { + "mastercard" => Some("2".to_string()), + _ => None, + }; + Some(BankOfAmericaConsumerAuthInformation { + ucaf_collection_indicator, + cavv: None, + ucaf_authentication_data: None, + xid: None, + directory_server_transaction_id: None, + specification_version: None, + }) + }, + ) } payments::WalletData::GooglePay(google_pay_data) => ( PaymentInformation::GooglePay(GooglePayPaymentInformation { @@ -394,6 +432,8 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest }, }), Some(PaymentSolution::GooglePay), + get_commerce_indicator(None), + None, ), payments::WalletData::AliPayQr(_) | payments::WalletData::AliPayRedirect(_) @@ -446,7 +486,7 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest action_list, action_token_types, authorization_options, - commerce_indicator: String::from("internet"), + commerce_indicator, payment_solution: solution.map(String::from), }; Ok(Self { @@ -454,6 +494,8 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest payment_information, order_information, client_reference_information, + merchant_defined_information, + consumer_authentication_information, }) } } @@ -769,10 +811,8 @@ impl let (action_list, action_token_types, authorization_options) = if item .router_data .request - .setup_future_usage - .map_or(false, |future_usage| { - matches!(future_usage, FutureUsage::OffSession) - }) { + .is_merchant_initiated_mandate_payment() + { ( Some(vec![BankOfAmericaActionsList::TokenCreate]), Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), @@ -790,14 +830,15 @@ impl }; // To distinguish between SetupMandate and Authorization flows. - let capture = if item.router_data.request.amount == 0 && authorization_options.is_some() { - Some(false) - } else { - Some(matches!( - item.router_data.request.capture_method, - Some(enums::CaptureMethod::Automatic) | None - )) - }; + let is_capture_automatic = matches!( + item.router_data.request.capture_method, + Some(enums::CaptureMethod::Automatic) | None + ); + let is_setup_mandate_payment = item.router_data.request.is_setup_mandate_payment(); + let capture = Some( + !((is_setup_mandate_payment && is_capture_automatic) + || (!is_setup_mandate_payment && !is_capture_automatic)), + ); Self { capture, @@ -1589,6 +1630,7 @@ fn get_payment_response( connector_mandate_id: Some(token_info.payment_instrument.id.expose()), payment_method_id: None, }); + Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), redirection_data: None, @@ -2082,7 +2124,8 @@ impl BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { let status = enums::AttemptStatus::foreign_from(( info_response.status.clone(), - item.data.request.is_auto_capture()?, + item.data.request.is_auto_capture()? + || item.data.request.is_setup_mandate_payment(), )); let response = get_payment_response((&info_response, status, item.http_code)); Ok(Self { @@ -2629,3 +2672,17 @@ impl } } } + +fn get_commerce_indicator(network: Option) -> String { + match network { + Some(card_network) => match card_network.to_lowercase().as_str() { + "amex" => "aesk", + "discover" => "dipb", + "mastercard" => "spa", + "visa" => "internet", + _ => "internet", + }, + None => "internet", + } + .to_string() +} diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 9a7149e6ac50..a7cb27ae4492 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -5,6 +5,7 @@ use api_models::{ payments::{self, BankDebitBilling, OrderDetailsWithAmount}, }; use base64::Engine; +use common_enums::FutureUsage; use common_utils::{ date_time, errors::ReportSwitchExt, @@ -611,6 +612,8 @@ pub trait PaymentsCompleteAuthorizeRequestData { fn get_email(&self) -> Result; fn get_redirect_response_payload(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; + fn is_setup_mandate_payment(&self) -> bool; + fn is_merchant_initiated_mandate_payment(&self) -> bool; } impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { @@ -640,6 +643,17 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { .clone() .ok_or_else(missing_field_err("complete_authorize_url")) } + + fn is_setup_mandate_payment(&self) -> bool { + matches!(self.amount, 0) && self.is_merchant_initiated_mandate_payment() + } + + fn is_merchant_initiated_mandate_payment(&self) -> bool { + self.setup_future_usage.map_or(false, |future_usage| { + matches!(future_usage, FutureUsage::OffSession) + }) + // add check for customer_acceptance + } } pub trait PaymentsSyncRequestData { From 74a1c17bd57292a0fefd124357f87c2e8be8859d Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Mon, 1 Apr 2024 15:13:01 +0530 Subject: [PATCH 06/15] remove BankOfAmericaZeroMandateRequest --- crates/router/src/connector/bankofamerica.rs | 2 +- .../src/connector/bankofamerica/transformers.rs | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index 87e6c56a9610..c974af5bfa14 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -326,7 +326,7 @@ impl ))?; Ok(RequestContent::Json(Box::new(connector_req))) } else { - let connector_req = bankofamerica::BankOfAmericaZeroMandateRequest::try_from(req)?; + let connector_req = bankofamerica::BankOfAmericaPaymentsRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } } diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 8742c23880fc..df3a51b4e8c1 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -280,20 +280,7 @@ pub struct BillTo { email: pii::Email, } -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct BankOfAmericaZeroMandateRequest { - processing_information: ProcessingInformation, - payment_information: PaymentInformation, - order_information: OrderInformationWithBill, - client_reference_information: ClientReferenceInformation, - #[serde(skip_serializing_if = "Option::is_none")] - merchant_defined_information: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - consumer_authentication_information: Option, -} - -impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaZeroMandateRequest { +impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from(item: &types::SetupMandateRouterData) -> Result { let email = item.request.get_email()?; @@ -779,6 +766,7 @@ impl None => "internet", } .to_string(); + Ok(Self { capture: Some(matches!( item.router_data.request.capture_method, @@ -835,6 +823,7 @@ impl Some(enums::CaptureMethod::Automatic) | None ); let is_setup_mandate_payment = item.router_data.request.is_setup_mandate_payment(); + let capture = Some( !((is_setup_mandate_payment && is_capture_automatic) || (!is_setup_mandate_payment && !is_capture_automatic)), From 5aaf804d682d6dcb5e970c54832d26c2d734d096 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Mon, 1 Apr 2024 16:19:57 +0530 Subject: [PATCH 07/15] resolve comment --- .../connector/bankofamerica/transformers.rs | 71 ++++++++++--------- crates/router/src/connector/utils.rs | 14 ---- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index df3a51b4e8c1..848dd80791a8 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -542,9 +542,7 @@ impl .clone() .unwrap_or(info_response.id), ), - incremental_authorization_allowed: Some( - mandate_status == enums::AttemptStatus::Authorized, - ), + incremental_authorization_allowed: None, }), }, ..item.data @@ -796,38 +794,34 @@ impl &BankOfAmericaConsumerAuthValidateResponse, ), ) -> Self { - let (action_list, action_token_types, authorization_options) = if item - .router_data - .request - .is_merchant_initiated_mandate_payment() - { - ( - Some(vec![BankOfAmericaActionsList::TokenCreate]), - Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), - Some(BankOfAmericaAuthorizationOptions { - initiator: Some(BankOfAmericaPaymentInitiator { - initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer), - credential_stored_on_file: Some(true), - stored_credential_used: None, + let (action_list, action_token_types, authorization_options) = + if is_merchant_initiated_mandate_payment(&item.router_data.request) { + ( + Some(vec![BankOfAmericaActionsList::TokenCreate]), + Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), + Some(BankOfAmericaAuthorizationOptions { + initiator: Some(BankOfAmericaPaymentInitiator { + initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, + }), + merchant_intitiated_transaction: None, }), - merchant_intitiated_transaction: None, - }), - ) - } else { - (None, None, None) - }; + ) + } else { + (None, None, None) + }; - // To distinguish between SetupMandate and Authorization flows. - let is_capture_automatic = matches!( - item.router_data.request.capture_method, - Some(enums::CaptureMethod::Automatic) | None - ); - let is_setup_mandate_payment = item.router_data.request.is_setup_mandate_payment(); + let is_setup_mandate_payment = is_setup_mandate_payment(&item.router_data.request); - let capture = Some( - !((is_setup_mandate_payment && is_capture_automatic) - || (!is_setup_mandate_payment && !is_capture_automatic)), - ); + let capture = if is_setup_mandate_payment { + Some(false) + } else { + Some(matches!( + item.router_data.request.capture_method, + Some(enums::CaptureMethod::Automatic) | None + )) + }; Self { capture, @@ -2114,7 +2108,7 @@ impl let status = enums::AttemptStatus::foreign_from(( info_response.status.clone(), item.data.request.is_auto_capture()? - || item.data.request.is_setup_mandate_payment(), + || is_setup_mandate_payment(&item.data.request), )); let response = get_payment_response((&info_response, status, item.http_code)); Ok(Self { @@ -2675,3 +2669,14 @@ fn get_commerce_indicator(network: Option) -> String { } .to_string() } + +fn is_setup_mandate_payment(item: &types::CompleteAuthorizeData) -> bool { + matches!(item.amount, 0) && is_merchant_initiated_mandate_payment(item) +} + +fn is_merchant_initiated_mandate_payment(item: &types::CompleteAuthorizeData) -> bool { + item.setup_future_usage.map_or(false, |future_usage| { + matches!(future_usage, common_enums::FutureUsage::OffSession) + }) + // add check for customer_acceptance +} diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index a7cb27ae4492..9a7149e6ac50 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -5,7 +5,6 @@ use api_models::{ payments::{self, BankDebitBilling, OrderDetailsWithAmount}, }; use base64::Engine; -use common_enums::FutureUsage; use common_utils::{ date_time, errors::ReportSwitchExt, @@ -612,8 +611,6 @@ pub trait PaymentsCompleteAuthorizeRequestData { fn get_email(&self) -> Result; fn get_redirect_response_payload(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; - fn is_setup_mandate_payment(&self) -> bool; - fn is_merchant_initiated_mandate_payment(&self) -> bool; } impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { @@ -643,17 +640,6 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { .clone() .ok_or_else(missing_field_err("complete_authorize_url")) } - - fn is_setup_mandate_payment(&self) -> bool { - matches!(self.amount, 0) && self.is_merchant_initiated_mandate_payment() - } - - fn is_merchant_initiated_mandate_payment(&self) -> bool { - self.setup_future_usage.map_or(false, |future_usage| { - matches!(future_usage, FutureUsage::OffSession) - }) - // add check for customer_acceptance - } } pub trait PaymentsSyncRequestData { From f6f0020127e419694e602c5e9dc3d59298833a5e Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Mon, 1 Apr 2024 18:05:10 +0530 Subject: [PATCH 08/15] rename is_mit to is_cit --- crates/router/src/connector/bankofamerica/transformers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 848dd80791a8..7a018350119c 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -795,7 +795,7 @@ impl ), ) -> Self { let (action_list, action_token_types, authorization_options) = - if is_merchant_initiated_mandate_payment(&item.router_data.request) { + if is_customer_initiated_mandate_payment(&item.router_data.request) { ( Some(vec![BankOfAmericaActionsList::TokenCreate]), Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), @@ -2674,7 +2674,7 @@ fn is_setup_mandate_payment(item: &types::CompleteAuthorizeData) -> bool { matches!(item.amount, 0) && is_merchant_initiated_mandate_payment(item) } -fn is_merchant_initiated_mandate_payment(item: &types::CompleteAuthorizeData) -> bool { +fn is_customer_initiated_mandate_payment(item: &types::CompleteAuthorizeData) -> bool { item.setup_future_usage.map_or(false, |future_usage| { matches!(future_usage, common_enums::FutureUsage::OffSession) }) From e54da5a612afdca8e0861288b858a3d9bf04eaca Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Mon, 1 Apr 2024 18:09:47 +0530 Subject: [PATCH 09/15] rename is_mit to cit --- crates/router/src/connector/bankofamerica/transformers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 7a018350119c..54e2dd47fa43 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -2671,7 +2671,7 @@ fn get_commerce_indicator(network: Option) -> String { } fn is_setup_mandate_payment(item: &types::CompleteAuthorizeData) -> bool { - matches!(item.amount, 0) && is_merchant_initiated_mandate_payment(item) + matches!(item.amount, 0) && is_customer_initiated_mandate_payment(item) } fn is_customer_initiated_mandate_payment(item: &types::CompleteAuthorizeData) -> bool { From 1143fca58a4e3c5e04f2af4d2ae8fdc37b49b042 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Tue, 2 Apr 2024 14:53:33 +0530 Subject: [PATCH 10/15] refactor(boa): resolve comments --- .../connector/bankofamerica/transformers.rs | 615 +++++++++--------- 1 file changed, 305 insertions(+), 310 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 54e2dd47fa43..3299e56c9a89 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -257,7 +257,7 @@ pub struct FluidData { #[serde(rename_all = "camelCase")] pub struct OrderInformationWithBill { amount_details: Amount, - bill_to: BillTo, + bill_to: Option, } #[derive(Debug, Serialize)] @@ -283,145 +283,15 @@ pub struct BillTo { impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from(item: &types::SetupMandateRouterData) -> Result { - let email = item.request.get_email()?; - let bill_to = build_bill_to(item.get_billing()?, email)?; - - let order_information = OrderInformationWithBill { - amount_details: Amount { - total_amount: "0".to_string(), - currency: item.request.currency, - }, - bill_to, - }; - let (action_list, action_token_types, authorization_options) = ( - Some(vec![BankOfAmericaActionsList::TokenCreate]), - Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), - Some(BankOfAmericaAuthorizationOptions { - initiator: Some(BankOfAmericaPaymentInitiator { - initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer), - credential_stored_on_file: Some(true), - stored_credential_used: None, - }), - merchant_intitiated_transaction: None, - }), - ); - - let client_reference_information = ClientReferenceInformation { - code: Some(item.connector_request_reference_id.clone()), - }; - - let merchant_defined_information = item.request.metadata.clone().map(|metadata| { - Vec::::foreign_from(metadata.peek().to_owned()) - }); - - let ( - payment_information, - solution, - commerce_indicator, - consumer_authentication_information, - ) = match item.request.payment_method_data.clone() { - api::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, - }; - ( - PaymentInformation::Cards(CardPaymentInformation { - card: Card { - number: ccard.card_number, - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - security_code: ccard.card_cvc, - card_type, - }, - }), - None, - get_commerce_indicator(None), - None, - ) - } - + match item.request.payment_method_data.clone() { + api::PaymentMethodData::Card(card_data) => Self::try_from((item, card_data)), api::PaymentMethodData::Wallet(wallet_data) => match wallet_data { payments::WalletData::ApplePay(apple_pay_data) => { - let (payment_information, solution) = match item.payment_method_token.clone() { - Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - let expiration_month = decrypt_data.get_expiry_month()?; - let expiration_year = decrypt_data.get_four_digit_expiry_year()?; - ( - PaymentInformation::ApplePay(ApplePayPaymentInformation { - tokenized_card: TokenizedCard { - number: decrypt_data.application_primary_account_number, - cryptogram: decrypt_data - .payment_data - .online_payment_cryptogram, - transaction_type: TransactionType::ApplePay, - expiration_year, - expiration_month, - }, - }), - Some(PaymentSolution::ApplePay), - ) - } - types::PaymentMethodToken::Token(_) => { - Err(unimplemented_payment_method!( - "Apple Pay", - "Manual", - "Bank Of America" - ))? - } - }, - None => ( - PaymentInformation::ApplePayToken(ApplePayTokenPaymentInformation { - fluid_data: FluidData { - value: Secret::from(apple_pay_data.payment_data), - }, - tokenized_card: ApplePayTokenizedCard { - transaction_type: TransactionType::ApplePay, - }, - }), - Some(PaymentSolution::ApplePay), - ), - }; - ( - payment_information, - solution, - get_commerce_indicator(Some(apple_pay_data.payment_method.network.clone())), - { - let ucaf_collection_indicator = match apple_pay_data - .payment_method - .network - .to_lowercase() - .as_str() - { - "mastercard" => Some("2".to_string()), - _ => None, - }; - Some(BankOfAmericaConsumerAuthInformation { - ucaf_collection_indicator, - cavv: None, - ucaf_authentication_data: None, - xid: None, - directory_server_transaction_id: None, - specification_version: None, - }) - }, - ) + Self::try_from((item, apple_pay_data)) + } + payments::WalletData::GooglePay(google_pay_data) => { + Self::try_from((item, google_pay_data)) } - payments::WalletData::GooglePay(google_pay_data) => ( - PaymentInformation::GooglePay(GooglePayPaymentInformation { - fluid_data: FluidData { - value: Secret::from( - consts::BASE64_ENGINE - .encode(google_pay_data.tokenization_data.token), - ), - }, - }), - Some(PaymentSolution::GooglePay), - get_commerce_indicator(None), - None, - ), payments::WalletData::AliPayQr(_) | payments::WalletData::AliPayRedirect(_) | payments::WalletData::AliPayHkRedirect(_) @@ -465,25 +335,7 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), ))? } - }; - - let processing_information = ProcessingInformation { - capture: Some(false), - capture_options: None, - action_list, - action_token_types, - authorization_options, - commerce_indicator, - payment_solution: solution.map(String::from), - }; - Ok(Self { - processing_information, - payment_information, - order_information, - client_reference_information, - merchant_defined_information, - consumer_authentication_information, - }) + } } } @@ -549,22 +401,7 @@ impl }) } BankOfAmericaSetupMandatesResponse::ErrorInformation(ref error_response) => { - let error_reason = error_response - .error_information - .message - .to_owned() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); - let error_message = error_response.error_information.reason.to_owned(); - let response = Err(types::ErrorResponse { - code: error_message - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason: Some(error_reason), - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: Some(error_response.id.clone()), - }); + let response = Err(types::ErrorResponse::from((error_response, item.http_code))); Ok(Self { response, status: enums::AttemptStatus::Failure, @@ -640,13 +477,13 @@ pub enum TransactionType { impl From<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, - BillTo, + Option, )> for OrderInformationWithBill { fn from( (item, bill_to): ( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, - BillTo, + Option, ), ) -> Self { Self { @@ -676,7 +513,7 @@ impl total_amount: item.amount.to_owned(), currency: item.router_data.request.currency, }, - bill_to, + bill_to: Some(bill_to), } } } @@ -713,18 +550,7 @@ impl .map_or(false, |mandate_details| { mandate_details.customer_acceptance.is_some() })) { - ( - Some(vec![BankOfAmericaActionsList::TokenCreate]), - Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), - Some(BankOfAmericaAuthorizationOptions { - initiator: Some(BankOfAmericaPaymentInitiator { - initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer), - credential_stored_on_file: Some(true), - stored_credential_used: None, - }), - merchant_intitiated_transaction: None, - }), - ) + get_boa_mandate_action_details() } else if item.router_data.request.connector_mandate_id().is_some() { let original_amount = item .router_data @@ -753,17 +579,7 @@ impl (None, None, None) }; - let commerce_indicator = match network { - Some(card_network) => match card_network.to_lowercase().as_str() { - "amex" => "aesk", - "discover" => "dipb", - "mastercard" => "spa", - "visa" => "internet", - _ => "internet", - }, - None => "internet", - } - .to_string(); + let commerce_indicator = get_commerce_indicator(network); Ok(Self { capture: Some(matches!( @@ -858,6 +674,14 @@ impl From<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>> } } +impl From<&types::SetupMandateRouterData> for ClientReferenceInformation { + fn from(item: &types::SetupMandateRouterData) -> Self { + Self { + code: Some(item.connector_request_reference_id.clone()), + } + } +} + impl ForeignFrom for Vec { fn foreign_from(metadata: Value) -> Self { let hashmap: std::collections::BTreeMap = @@ -922,21 +746,7 @@ impl let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; let order_information = OrderInformationWithBill::from((item, bill_to)); - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, - }; - - let payment_information = PaymentInformation::Cards(CardPaymentInformation { - card: Card { - number: ccard.card_number, - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - security_code: ccard.card_cvc, - card_type, - }, - }); + let payment_information = PaymentInformation::try_from(&ccard)?; let client_reference_information = ClientReferenceInformation::from(item); let three_ds_info: BankOfAmericaThreeDSMetadata = item @@ -997,21 +807,8 @@ impl ) -> Result { let email = item.router_data.request.get_email()?; let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; - let order_information = OrderInformationWithBill::from((item, bill_to)); - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, - }; - let payment_information = PaymentInformation::Cards(CardPaymentInformation { - card: Card { - number: ccard.card_number, - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - security_code: ccard.card_cvc, - card_type, - }, - }); + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + let payment_information = PaymentInformation::try_from(&ccard)?; let processing_information = ProcessingInformation::try_from((item, None, None))?; let client_reference_information = ClientReferenceInformation::from(item); let merchant_defined_information = @@ -1047,24 +844,14 @@ impl ) -> Result { let email = item.router_data.request.get_email()?; let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; - let order_information = OrderInformationWithBill::from((item, bill_to)); + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); let processing_information = ProcessingInformation::try_from(( item, Some(PaymentSolution::ApplePay), Some(apple_pay_wallet_data.payment_method.network.clone()), ))?; let client_reference_information = ClientReferenceInformation::from(item); - let expiration_month = apple_pay_data.get_expiry_month()?; - let expiration_year = apple_pay_data.get_four_digit_expiry_year()?; - let payment_information = PaymentInformation::ApplePay(ApplePayPaymentInformation { - tokenized_card: TokenizedCard { - number: apple_pay_data.application_primary_account_number, - cryptogram: apple_pay_data.payment_data.online_payment_cryptogram, - transaction_type: TransactionType::ApplePay, - expiration_year, - expiration_month, - }, - }); + let payment_information = PaymentInformation::try_from(&apple_pay_data)?; let merchant_defined_information = item.router_data.request.metadata.clone().map(|metadata| { Vec::::foreign_from(metadata.peek().to_owned()) @@ -1111,14 +898,8 @@ impl ) -> Result { let email = item.router_data.request.get_email()?; let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; - let order_information = OrderInformationWithBill::from((item, bill_to)); - let payment_information = PaymentInformation::GooglePay(GooglePayPaymentInformation { - fluid_data: FluidData { - value: Secret::from( - consts::BASE64_ENGINE.encode(google_pay_data.tokenization_data.token), - ), - }, - }); + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + let payment_information = PaymentInformation::from(&google_pay_data); let processing_information = ProcessingInformation::try_from((item, Some(PaymentSolution::GooglePay), None))?; let client_reference_information = ClientReferenceInformation::from(item); @@ -1169,8 +950,8 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> let email = item.router_data.request.get_email()?; let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; - let order_information = - OrderInformationWithBill::from((item, bill_to)); + let order_information: OrderInformationWithBill = + OrderInformationWithBill::from((item, Some(bill_to))); let processing_information = ProcessingInformation::try_from(( item, @@ -1179,16 +960,8 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> ))?; let client_reference_information = ClientReferenceInformation::from(item); - let payment_information = PaymentInformation::ApplePayToken( - ApplePayTokenPaymentInformation { - fluid_data: FluidData { - value: Secret::from(apple_pay_data.payment_data), - }, - tokenized_card: ApplePayTokenizedCard { - transaction_type: TransactionType::ApplePay, - }, - }, - ); + let payment_information = + PaymentInformation::from(&apple_pay_data); let merchant_defined_information = item.router_data.request.metadata.clone().map(|metadata| { Vec::::foreign_from( @@ -1312,20 +1085,7 @@ impl TryFrom<(&payments::PaymentMethodData, String)> for BankOfAmericaAuthSetupR ) -> Result { match payment_method_data.clone() { payments::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, - }; - let payment_information = PaymentInformation::Cards(CardPaymentInformation { - card: Card { - number: ccard.card_number, - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - security_code: ccard.card_cvc, - card_type, - }, - }); + let payment_information = PaymentInformation::try_from(&ccard)?; let client_reference_information = ClientReferenceInformation { code: Some(connector_request_reference_id), }; @@ -1374,9 +1134,7 @@ impl let payment_instrument = BankOfAmericaPaymentInstrument { id: connector_mandate_id.into(), }; - let email = item.router_data.request.get_email()?; - let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; - let order_information = OrderInformationWithBill::from((item, bill_to)); + let order_information = OrderInformationWithBill::from((item, None)); let payment_information = PaymentInformation::MandatePayment(MandatePaymentInformation { payment_instrument }); let client_reference_information = ClientReferenceInformation::from(item); @@ -1770,22 +1528,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>> }, )?; let payment_information = match payment_method_data { - payments::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, - }; - Ok(PaymentInformation::Cards(CardPaymentInformation { - card: Card { - number: ccard.card_number, - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - security_code: ccard.card_cvc, - card_type, - }, - })) - } + payments::PaymentMethodData::Card(ccard) => PaymentInformation::try_from(&ccard), payments::PaymentMethodData::Wallet(_) | payments::PaymentMethodData::CardRedirect(_) | payments::PaymentMethodData::PayLater(_) @@ -1802,6 +1545,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>> Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), )) + .into_report() } }?; @@ -1835,7 +1579,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>> let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; let order_information = OrderInformationWithBill { amount_details, - bill_to, + bill_to: Some(bill_to), }; Ok(Self::AuthEnrollment(BankOfAmericaAuthEnrollmentRequest { payment_information, @@ -2058,22 +1802,7 @@ impl } } BankOfAmericaPreProcessingResponse::ErrorInformation(ref error_response) => { - let error_reason = error_response - .error_information - .message - .to_owned() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); - let error_message = error_response.error_information.reason.to_owned(); - let response = Err(types::ErrorResponse { - code: error_message - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason: Some(error_reason), - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: Some(error_response.id.clone()), - }); + let response = Err(types::ErrorResponse::from((error_response, item.http_code))); Ok(Self { response, status: enums::AttemptStatus::AuthenticationFailed, @@ -2656,6 +2385,272 @@ impl } } +impl TryFrom<(&types::SetupMandateRouterData, payments::Card)> for BankOfAmericaPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + (item, ccard): (&types::SetupMandateRouterData, payments::Card), + ) -> Result { + let order_information = OrderInformationWithBill::try_from(item)?; + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + let payment_information = PaymentInformation::try_from(&ccard)?; + let processing_information = ProcessingInformation::try_from((None, None))?; + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: None, + merchant_defined_information, + }) + } +} + +impl TryFrom<(&types::SetupMandateRouterData, payments::ApplePayWalletData)> + for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, apple_pay_data): (&types::SetupMandateRouterData, payments::ApplePayWalletData), + ) -> Result { + let order_information = OrderInformationWithBill::try_from(item)?; + + let client_reference_information = ClientReferenceInformation::from(item); + + let merchant_defined_information = item.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + + let payment_information = match item.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + PaymentInformation::try_from(&decrypt_data)? + } + types::PaymentMethodToken::Token(_) => Err(unimplemented_payment_method!( + "Apple Pay", + "Manual", + "Bank Of America" + ))?, + }, + None => PaymentInformation::from(&apple_pay_data), + }; + + let processing_information = ProcessingInformation::try_from(( + Some(PaymentSolution::ApplePay), + Some(apple_pay_data.payment_method.network.clone()), + ))?; + + let ucaf_collection_indicator = match apple_pay_data + .payment_method + .network + .to_lowercase() + .as_str() + { + "mastercard" => Some("2".to_string()), + _ => None, + }; + + let consumer_authentication_information = Some(BankOfAmericaConsumerAuthInformation { + ucaf_collection_indicator, + cavv: None, + ucaf_authentication_data: None, + xid: None, + directory_server_transaction_id: None, + specification_version: None, + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information, + }) + } +} + +impl + TryFrom<( + &types::SetupMandateRouterData, + payments::GooglePayWalletData, + )> for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, google_pay_data): ( + &types::SetupMandateRouterData, + payments::GooglePayWalletData, + ), + ) -> Result { + let order_information = OrderInformationWithBill::try_from(item)?; + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + let payment_information = PaymentInformation::from(&google_pay_data); + let processing_information = + ProcessingInformation::try_from((Some(PaymentSolution::GooglePay), None))?; + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, + }) + } +} + +impl TryFrom<(Option, Option)> for ProcessingInformation { + type Error = error_stack::Report; + fn try_from( + (solution, network): (Option, Option), + ) -> Result { + let (action_list, action_token_types, authorization_options) = + get_boa_mandate_action_details(); + let commerce_indicator = get_commerce_indicator(network); + + Ok(ProcessingInformation { + capture: Some(false), + capture_options: None, + action_list, + action_token_types, + authorization_options, + commerce_indicator, + payment_solution: solution.map(String::from), + }) + } +} + +impl TryFrom<&types::SetupMandateRouterData> for OrderInformationWithBill { + type Error = error_stack::Report; + + fn try_from(item: &types::SetupMandateRouterData) -> Result { + let email = item.request.get_email()?; + let bill_to = build_bill_to(item.get_billing()?, email)?; + + Ok(OrderInformationWithBill { + amount_details: Amount { + total_amount: "0".to_string(), + currency: item.request.currency, + }, + bill_to: Some(bill_to), + }) + } +} + +impl TryFrom<&payments::Card> for PaymentInformation { + type Error = error_stack::Report; + + fn try_from(ccard: &payments::Card) -> Result { + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + Ok(PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number.clone(), + expiration_month: ccard.card_exp_month.clone(), + expiration_year: ccard.card_exp_year.clone(), + security_code: ccard.card_cvc.clone(), + card_type, + }, + })) + } +} + +impl TryFrom<&Box> for PaymentInformation { + type Error = error_stack::Report; + + fn try_from(apple_pay_data: &Box) -> Result { + let expiration_month = apple_pay_data.get_expiry_month()?; + let expiration_year = apple_pay_data.get_four_digit_expiry_year()?; + + Ok(PaymentInformation::ApplePay(ApplePayPaymentInformation { + tokenized_card: TokenizedCard { + number: apple_pay_data.application_primary_account_number.clone(), + cryptogram: apple_pay_data + .payment_data + .online_payment_cryptogram + .clone(), + transaction_type: TransactionType::ApplePay, + expiration_year, + expiration_month, + }, + })) + } +} + +impl From<&payments::ApplePayWalletData> for PaymentInformation { + fn from(apple_pay_data: &payments::ApplePayWalletData) -> Self { + PaymentInformation::ApplePayToken(ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data.clone()), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }) + } +} + +impl From<&payments::GooglePayWalletData> for PaymentInformation { + fn from(google_pay_data: &payments::GooglePayWalletData) -> Self { + PaymentInformation::GooglePay(GooglePayPaymentInformation { + fluid_data: FluidData { + value: Secret::from( + consts::BASE64_ENGINE.encode(google_pay_data.tokenization_data.token.clone()), + ), + }, + }) + } +} + +impl From<(&BankOfAmericaErrorInformationResponse, u16)> for types::ErrorResponse { + fn from((error_response, status_code): (&BankOfAmericaErrorInformationResponse, u16)) -> Self { + let error_reason = error_response + .error_information + .message + .to_owned() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason.to_owned(); + types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + } + } +} + +fn get_boa_mandate_action_details() -> ( + Option>, + Option>, + Option, +) { + ( + Some(vec![BankOfAmericaActionsList::TokenCreate]), + Some(vec![BankOfAmericaActionsTokenType::PaymentInstrument]), + Some(BankOfAmericaAuthorizationOptions { + initiator: Some(BankOfAmericaPaymentInitiator { + initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, + }), + merchant_intitiated_transaction: None, + }), + ) +} + fn get_commerce_indicator(network: Option) -> String { match network { Some(card_network) => match card_network.to_lowercase().as_str() { From 2c70ce0909890c9d7c2a769b2907f70cf5b09693 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Tue, 2 Apr 2024 15:50:26 +0530 Subject: [PATCH 11/15] resolve merge conflicts --- .../connector/bankofamerica/transformers.rs | 53 ++++++++----------- crates/router/src/connector/utils.rs | 2 +- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index db660b589589..532c8fa98fb0 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -285,8 +285,8 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from(item: &types::SetupMandateRouterData) -> Result { match item.request.payment_method_data.clone() { - api::PaymentMethodData::Card(card_data) => Self::try_from((item, card_data)), - api::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + domain::PaymentMethodData::Card(card_data) => Self::try_from((item, card_data)), + domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { payments::WalletData::ApplePay(apple_pay_data) => { Self::try_from((item, apple_pay_data)) } @@ -320,18 +320,18 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), ))?, }, - payments::PaymentMethodData::CardRedirect(_) - | payments::PaymentMethodData::PayLater(_) - | payments::PaymentMethodData::BankRedirect(_) - | payments::PaymentMethodData::BankDebit(_) - | payments::PaymentMethodData::BankTransfer(_) - | payments::PaymentMethodData::Crypto(_) - | payments::PaymentMethodData::MandatePayment - | payments::PaymentMethodData::Reward - | payments::PaymentMethodData::Upi(_) - | payments::PaymentMethodData::Voucher(_) - | payments::PaymentMethodData::GiftCard(_) - | payments::PaymentMethodData::CardToken(_) => { + domain::PaymentMethodData::CardRedirect(_) + | domain::PaymentMethodData::PayLater(_) + | domain::PaymentMethodData::BankRedirect(_) + | domain::PaymentMethodData::BankDebit(_) + | domain::PaymentMethodData::BankTransfer(_) + | domain::PaymentMethodData::Crypto(_) + | domain::PaymentMethodData::MandatePayment + | domain::PaymentMethodData::Reward + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::CardToken(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), ))? @@ -1076,13 +1076,10 @@ pub struct BankOfAmericaAuthSetupRequest { client_reference_information: ClientReferenceInformation, } -impl TryFrom<(&payments::PaymentMethodData, String)> for BankOfAmericaAuthSetupRequest { +impl TryFrom<(&domain::PaymentMethodData, String)> for BankOfAmericaAuthSetupRequest { type Error = error_stack::Report; fn try_from( - (payment_method_data, connector_request_reference_id): ( - &payments::PaymentMethodData, - String, - ), + (payment_method_data, connector_request_reference_id): (&domain::PaymentMethodData, String), ) -> Result { match payment_method_data.clone() { domain::PaymentMethodData::Card(ccard) => { @@ -1545,8 +1542,8 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>> | domain::PaymentMethodData::CardToken(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), - )) - .into_report() + ) + .into()) } }?; @@ -2385,10 +2382,10 @@ impl } } -impl TryFrom<(&types::SetupMandateRouterData, payments::Card)> for BankOfAmericaPaymentsRequest { +impl TryFrom<(&types::SetupMandateRouterData, domain::Card)> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( - (item, ccard): (&types::SetupMandateRouterData, payments::Card), + (item, ccard): (&types::SetupMandateRouterData, domain::Card), ) -> Result { let order_information = OrderInformationWithBill::try_from(item)?; let client_reference_information = ClientReferenceInformation::from(item); @@ -2416,13 +2413,10 @@ impl TryFrom<(&types::SetupMandateRouterData, payments::ApplePayWalletData)> (item, apple_pay_data): (&types::SetupMandateRouterData, payments::ApplePayWalletData), ) -> Result { let order_information = OrderInformationWithBill::try_from(item)?; - let client_reference_information = ClientReferenceInformation::from(item); - let merchant_defined_information = item.request.metadata.clone().map(|metadata| { Vec::::foreign_from(metadata.peek().to_owned()) }); - let payment_information = match item.payment_method_token.clone() { Some(payment_method_token) => match payment_method_token { types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { @@ -2436,12 +2430,10 @@ impl TryFrom<(&types::SetupMandateRouterData, payments::ApplePayWalletData)> }, None => PaymentInformation::from(&apple_pay_data), }; - let processing_information = ProcessingInformation::try_from(( Some(PaymentSolution::ApplePay), Some(apple_pay_data.payment_method.network.clone()), ))?; - let ucaf_collection_indicator = match apple_pay_data .payment_method .network @@ -2451,7 +2443,6 @@ impl TryFrom<(&types::SetupMandateRouterData, payments::ApplePayWalletData)> "mastercard" => Some("2".to_string()), _ => None, }; - let consumer_authentication_information = Some(BankOfAmericaConsumerAuthInformation { ucaf_collection_indicator, cavv: None, @@ -2543,10 +2534,10 @@ impl TryFrom<&types::SetupMandateRouterData> for OrderInformationWithBill { } } -impl TryFrom<&payments::Card> for PaymentInformation { +impl TryFrom<&domain::Card> for PaymentInformation { type Error = error_stack::Report; - fn try_from(ccard: &payments::Card) -> Result { + fn try_from(ccard: &domain::Card) -> Result { let card_issuer = ccard.get_card_issuer(); let card_type = match card_issuer { Ok(issuer) => Some(String::from(issuer)), diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index b62384f6bf7f..5bdf8b501ad7 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -473,7 +473,7 @@ impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { self.email.clone().ok_or_else(missing_field_err("email")) } fn is_card(&self) -> bool { - matches!(self.payment_method_data, api::PaymentMethodData::Card(_)) + matches!(self.payment_method_data, domain::PaymentMethodData::Card(_)) } } pub trait PaymentsAuthorizeRequestData { From fd5be6178ba101399bf8a09a66681cf56e7372eb Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Tue, 2 Apr 2024 16:15:07 +0530 Subject: [PATCH 12/15] fix clippy --- .../src/connector/bankofamerica/transformers.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 532c8fa98fb0..f42e75e74b24 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -2505,7 +2505,7 @@ impl TryFrom<(Option, Option)> for ProcessingInformatio get_boa_mandate_action_details(); let commerce_indicator = get_commerce_indicator(network); - Ok(ProcessingInformation { + Ok(Self { capture: Some(false), capture_options: None, action_list, @@ -2524,7 +2524,7 @@ impl TryFrom<&types::SetupMandateRouterData> for OrderInformationWithBill { let email = item.request.get_email()?; let bill_to = build_bill_to(item.get_billing()?, email)?; - Ok(OrderInformationWithBill { + Ok(Self { amount_details: Amount { total_amount: "0".to_string(), currency: item.request.currency, @@ -2543,7 +2543,7 @@ impl TryFrom<&domain::Card> for PaymentInformation { Ok(issuer) => Some(String::from(issuer)), Err(_) => None, }; - Ok(PaymentInformation::Cards(CardPaymentInformation { + Ok(Self::Cards(CardPaymentInformation { card: Card { number: ccard.card_number.clone(), expiration_month: ccard.card_exp_month.clone(), @@ -2562,7 +2562,7 @@ impl TryFrom<&Box> for PaymentInformation { let expiration_month = apple_pay_data.get_expiry_month()?; let expiration_year = apple_pay_data.get_four_digit_expiry_year()?; - Ok(PaymentInformation::ApplePay(ApplePayPaymentInformation { + Ok(Self::ApplePay(ApplePayPaymentInformation { tokenized_card: TokenizedCard { number: apple_pay_data.application_primary_account_number.clone(), cryptogram: apple_pay_data @@ -2579,7 +2579,7 @@ impl TryFrom<&Box> for PaymentInformation { impl From<&payments::ApplePayWalletData> for PaymentInformation { fn from(apple_pay_data: &payments::ApplePayWalletData) -> Self { - PaymentInformation::ApplePayToken(ApplePayTokenPaymentInformation { + Self::ApplePayToken(ApplePayTokenPaymentInformation { fluid_data: FluidData { value: Secret::from(apple_pay_data.payment_data.clone()), }, @@ -2592,7 +2592,7 @@ impl From<&payments::ApplePayWalletData> for PaymentInformation { impl From<&payments::GooglePayWalletData> for PaymentInformation { fn from(google_pay_data: &payments::GooglePayWalletData) -> Self { - PaymentInformation::GooglePay(GooglePayPaymentInformation { + Self::GooglePay(GooglePayPaymentInformation { fluid_data: FluidData { value: Secret::from( consts::BASE64_ENGINE.encode(google_pay_data.tokenization_data.token.clone()), @@ -2610,7 +2610,7 @@ impl From<(&BankOfAmericaErrorInformationResponse, u16)> for types::ErrorRespons .to_owned() .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); let error_message = error_response.error_information.reason.to_owned(); - types::ErrorResponse { + Self { code: error_message .clone() .unwrap_or(consts::NO_ERROR_CODE.to_string()), From 2d293d081a829042a2b790b0d49f82600867d106 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Tue, 2 Apr 2024 16:58:22 +0530 Subject: [PATCH 13/15] add comment --- crates/router/src/connector/bankofamerica/transformers.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index f42e75e74b24..87d7573e8ff8 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -2496,6 +2496,7 @@ impl } } +// specific for setupMandate flow impl TryFrom<(Option, Option)> for ProcessingInformation { type Error = error_stack::Report; fn try_from( @@ -2506,7 +2507,7 @@ impl TryFrom<(Option, Option)> for ProcessingInformatio let commerce_indicator = get_commerce_indicator(network); Ok(Self { - capture: Some(false), + capture: Some(false), capture_options: None, action_list, action_token_types, From 50f3a54a049c1727f4fa3b72acf7542015700d1a Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:35:18 +0000 Subject: [PATCH 14/15] chore: run formatter --- crates/router/src/connector/bankofamerica/transformers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 87d7573e8ff8..3fe4fb3972d0 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -2507,7 +2507,7 @@ impl TryFrom<(Option, Option)> for ProcessingInformatio let commerce_indicator = get_commerce_indicator(network); Ok(Self { - capture: Some(false), + capture: Some(false), capture_options: None, action_list, action_token_types, From 9c26162e515d823c4a0c924a60e6e1081334f6d7 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Tue, 2 Apr 2024 18:34:11 +0530 Subject: [PATCH 15/15] add bill_to to mit --- .../router/src/connector/bankofamerica/transformers.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 3fe4fb3972d0..d09e6ea5a0ec 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -1132,7 +1132,14 @@ impl let payment_instrument = BankOfAmericaPaymentInstrument { id: connector_mandate_id.into(), }; - let order_information = OrderInformationWithBill::from((item, None)); + let email = item.router_data.request.get_email().ok(); + let bill_to = email.and_then(|email_id| { + item.router_data + .get_billing() + .ok() + .and_then(|billing_details| build_bill_to(billing_details, email_id).ok()) + }); + let order_information = OrderInformationWithBill::from((item, bill_to)); let payment_information = PaymentInformation::MandatePayment(MandatePaymentInformation { payment_instrument }); let client_reference_information = ClientReferenceInformation::from(item);