From eca2141a8ea4f984a846674bf872fe6423ba1665 Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL Date: Tue, 5 Dec 2023 20:09:56 +0530 Subject: [PATCH 1/5] feat(connector): [BANKOFAMERICA] Implement Apple Pay --- .../connector/bankofamerica/transformers.rs | 89 ++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index e31a69669c6d..690f5ebb1811 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -1,7 +1,7 @@ use api_models::payments; use base64::Engine; use common_utils::pii; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ @@ -16,6 +16,7 @@ use crate::{ api::{self, enums as api_enums}, storage::enums, transformers::ForeignFrom, + ApplePayPredecryptData, }, }; @@ -110,11 +111,18 @@ pub struct GooglePayPaymentInformation { fluid_data: FluidData, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayPaymentInformation { + tokenized_card: TokenizedCard, +} + #[derive(Debug, Serialize)] #[serde(untagged)] pub enum PaymentInformation { Cards(CardPaymentInformation), GooglePay(GooglePayPaymentInformation), + ApplePay(ApplePayPaymentInformation), } #[derive(Debug, Serialize)] @@ -128,6 +136,16 @@ pub struct Card { card_type: Option, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenizedCard { + number: Secret, + expiration_month: Secret, + expiration_year: Secret, + cryptogram: Secret, + transaction_type: String, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct FluidData { @@ -320,6 +338,63 @@ impl } } +impl + TryFrom<( + &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + )> for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, apple_pay_data): ( + &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + ), + ) -> 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 processing_information = + ProcessingInformation::from((item, Some(PaymentSolution::ApplePay))); + let client_reference_information = ClientReferenceInformation::from(item); + + let expiration_year = Secret::new(format!( + "20{}", + apple_pay_data + .clone() + .application_expiration_date + .peek() + .get(0..2) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + )); + let expiration_month = Secret::new( + apple_pay_data + .clone() + .application_expiration_date + .peek() + .get(2..4) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .to_owned(), + ); + 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: "1".to_string(), + expiration_year, + expiration_month, + }, + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + }) + } +} + impl TryFrom<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, @@ -368,6 +443,17 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> 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(_) => { + let payment_method_token = item.router_data.get_payment_method_token()?; + match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + Self::try_from((item, decrypt_data)) + } + types::PaymentMethodToken::Token(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + } + } payments::WalletData::GooglePay(google_pay_data) => { Self::try_from((item, google_pay_data)) } @@ -378,7 +464,6 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> | payments::WalletData::KakaoPayRedirect(_) | payments::WalletData::GoPayRedirect(_) | payments::WalletData::GcashRedirect(_) - | payments::WalletData::ApplePay(_) | payments::WalletData::ApplePayRedirect(_) | payments::WalletData::ApplePayThirdPartySdk(_) | payments::WalletData::DanaRedirect {} From 1d4b1432b92ea7d641bd9d6605285256ee5d2ece Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL Date: Wed, 6 Dec 2023 12:43:37 +0530 Subject: [PATCH 2/5] feat(connector): [BANKOFAMERICA] Resolve PR Comments --- .../connector/bankofamerica/transformers.rs | 35 +++++++------------ crates/router/src/connector/utils.rs | 29 ++++++++++++++- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 690f5ebb1811..2a5b805e796a 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -1,13 +1,13 @@ use api_models::payments; use base64::Engine; use common_utils::pii; -use masking::{PeekInterface, Secret}; +use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ self, AddressDetailsData, CardData, CardIssuer, PaymentsAuthorizeRequestData, - PaymentsSyncRequestData, RouterData, + PaymentsSyncRequestData, RouterData, ApplePayDecrypt, }, consts, core::errors, @@ -143,7 +143,7 @@ pub struct TokenizedCard { expiration_month: Secret, expiration_year: Secret, cryptogram: Secret, - transaction_type: String, + transaction_type: TransactionType, } #[derive(Debug, Serialize)] @@ -233,6 +233,12 @@ impl From for String { } } +#[derive(Debug, Serialize)] +pub enum TransactionType { + #[serde(rename = "1")] + ApplePay, +} + impl From<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, @@ -358,29 +364,14 @@ impl ProcessingInformation::from((item, Some(PaymentSolution::ApplePay))); let client_reference_information = ClientReferenceInformation::from(item); - let expiration_year = Secret::new(format!( - "20{}", - apple_pay_data - .clone() - .application_expiration_date - .peek() - .get(0..2) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - )); - let expiration_month = Secret::new( - apple_pay_data - .clone() - .application_expiration_date - .peek() - .get(2..4) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - .to_owned(), - ); + let expiration_month = apple_pay_data.get_applepay_decrypted_expiration_month()?; + let expiration_year = apple_pay_data.get_applepay_decrypted_expiration_year_4_digit()?; + 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: "1".to_string(), + transaction_type: TransactionType::ApplePay, expiration_year, expiration_month, }, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 803c511f3a6b..b8e80b0e126d 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -26,7 +26,7 @@ use crate::{ pii::PeekInterface, types::{ self, api, storage::payment_attempt::PaymentAttemptExt, transformers::ForeignTryFrom, - PaymentsCancelData, ResponseId, + ApplePayPredecryptData, PaymentsCancelData, ResponseId, }, utils::{OptionExt, ValueExt}, }; @@ -853,6 +853,33 @@ impl ApplePay for payments::ApplePayWalletData { } } +pub trait ApplePayDecrypt { + fn get_applepay_decrypted_expiration_month(&self) -> Result, Error>; + fn get_applepay_decrypted_expiration_year_4_digit(&self) -> Result, Error>; +} + +impl ApplePayDecrypt for Box { + fn get_applepay_decrypted_expiration_year_4_digit(&self) -> Result, Error> { + Ok(Secret::new(format!( + "20{}", + self.application_expiration_date + .peek() + .get(0..2) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + ))) + } + + fn get_applepay_decrypted_expiration_month(&self) -> Result, Error> { + Ok(Secret::new( + self.application_expiration_date + .peek() + .get(2..4) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .to_owned(), + )) + } +} + pub trait CryptoData { fn get_pay_currency(&self) -> Result; } From e25902bd00c7c432e56bbd15bf81329f91960120 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 07:14:22 +0000 Subject: [PATCH 3/5] chore: run formatter --- 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 2a5b805e796a..719e23974deb 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ - self, AddressDetailsData, CardData, CardIssuer, PaymentsAuthorizeRequestData, - PaymentsSyncRequestData, RouterData, ApplePayDecrypt, + self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer, + PaymentsAuthorizeRequestData, PaymentsSyncRequestData, RouterData, }, consts, core::errors, From 1517c4a973a52b5bc09bb2060e5e33ab6c958b99 Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL Date: Wed, 6 Dec 2023 13:29:06 +0530 Subject: [PATCH 4/5] feat(connector): [BANKOFAMERICA] Resolve PR Comments 2 --- crates/router/src/configs/defaults.rs | 89 ++++++++++++++++++- .../src/connector/checkout/transformers.rs | 26 ++---- .../src/connector/stripe/transformers.rs | 27 ++---- 3 files changed, 101 insertions(+), 41 deletions(-) diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index d529ae034a86..8367f8af36e4 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -4382,6 +4382,93 @@ impl Default for super::settings::RequiredFields { non_mandate: HashMap::new(), common: HashMap::new(), } + ), + ( + enums::Connector::Bankofamerica, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } ) ]), }, @@ -4484,7 +4571,7 @@ impl Default for super::settings::RequiredFields { ), common: HashMap::new(), } - ), + ) ]), }, ), diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index ebe02f30d5ff..a7593191f357 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -1,12 +1,12 @@ use common_utils::{errors::CustomResult, ext_traits::ByteSliceExt}; use error_stack::{IntoReport, ResultExt}; -use masking::{ExposeInterface, PeekInterface, Secret}; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use url::Url; use crate::{ - connector::utils::{self, PaymentsCaptureRequestData, RouterData, WalletData}, + connector::utils::{self, ApplePayDecrypt, PaymentsCaptureRequestData, RouterData, WalletData}, consts, core::errors, services, @@ -304,24 +304,10 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme })) } types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - let expiry_year_4_digit = Secret::new(format!( - "20{}", - decrypt_data - .clone() - .application_expiration_date - .peek() - .get(0..2) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - )); - let exp_month = Secret::new( - decrypt_data - .clone() - .application_expiration_date - .peek() - .get(2..4) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - .to_owned(), - ); + let exp_month = + decrypt_data.get_applepay_decrypted_expiration_month()?; + let expiry_year_4_digit = + decrypt_data.get_applepay_decrypted_expiration_year_4_digit()?; Ok(PaymentSource::ApplePayPredecrypt(Box::new( ApplePayPredecrypt { token: decrypt_data.application_primary_account_number, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 182479604539..979dc0466403 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -8,7 +8,7 @@ use common_utils::{ }; use data_models::mandates::AcceptanceType; use error_stack::{IntoReport, ResultExt}; -use masking::{ExposeInterface, ExposeOptionInterface, PeekInterface, Secret}; +use masking::{ExposeInterface, ExposeOptionInterface, Secret}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use url::Url; @@ -16,7 +16,9 @@ use uuid::Uuid; use crate::{ collect_missing_value_keys, - connector::utils::{self as connector_util, ApplePay, PaymentsPreProcessingData, RouterData}, + connector::utils::{ + self as connector_util, ApplePay, ApplePayDecrypt, PaymentsPreProcessingData, RouterData, + }, core::errors, services, types::{ @@ -1473,24 +1475,9 @@ impl TryFrom<(&payments::WalletData, Option)> if let Some(types::PaymentMethodToken::ApplePayDecrypt(decrypt_data)) = payment_method_token { - let expiry_year_4_digit = Secret::new(format!( - "20{}", - decrypt_data - .clone() - .application_expiration_date - .peek() - .get(0..2) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - )); - let exp_month = Secret::new( - decrypt_data - .clone() - .application_expiration_date - .peek() - .get(2..4) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - .to_owned(), - ); + let expiry_year_4_digit = + decrypt_data.get_applepay_decrypted_expiration_year_4_digit()?; + let exp_month = decrypt_data.get_applepay_decrypted_expiration_month()?; Some(Self::Wallet(StripeWallet::ApplePayPredecryptToken( Box::new(StripeApplePayPredecrypt { From a4d514d507b0575e70bb110172db2d945ed6c798 Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL Date: Wed, 6 Dec 2023 14:39:16 +0530 Subject: [PATCH 5/5] feat(connector): [BANKOFAMERICA] Resolve PR Comments 3 --- crates/router/src/connector/bankofamerica/transformers.rs | 4 ++-- crates/router/src/connector/checkout/transformers.rs | 6 ++---- crates/router/src/connector/stripe/transformers.rs | 5 ++--- crates/router/src/connector/utils.rs | 8 ++++---- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 719e23974deb..bbec9022835c 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -364,8 +364,8 @@ impl ProcessingInformation::from((item, Some(PaymentSolution::ApplePay))); let client_reference_information = ClientReferenceInformation::from(item); - let expiration_month = apple_pay_data.get_applepay_decrypted_expiration_month()?; - let expiration_year = apple_pay_data.get_applepay_decrypted_expiration_year_4_digit()?; + 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 { diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index a7593191f357..37c038c22afe 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -304,10 +304,8 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme })) } types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - let exp_month = - decrypt_data.get_applepay_decrypted_expiration_month()?; - let expiry_year_4_digit = - decrypt_data.get_applepay_decrypted_expiration_year_4_digit()?; + let exp_month = decrypt_data.get_expiry_month()?; + let expiry_year_4_digit = decrypt_data.get_four_digit_expiry_year()?; Ok(PaymentSource::ApplePayPredecrypt(Box::new( ApplePayPredecrypt { token: decrypt_data.application_primary_account_number, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 979dc0466403..fad029c1c9db 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1475,9 +1475,8 @@ impl TryFrom<(&payments::WalletData, Option)> if let Some(types::PaymentMethodToken::ApplePayDecrypt(decrypt_data)) = payment_method_token { - let expiry_year_4_digit = - decrypt_data.get_applepay_decrypted_expiration_year_4_digit()?; - let exp_month = decrypt_data.get_applepay_decrypted_expiration_month()?; + let expiry_year_4_digit = decrypt_data.get_four_digit_expiry_year()?; + let exp_month = decrypt_data.get_expiry_month()?; Some(Self::Wallet(StripeWallet::ApplePayPredecryptToken( Box::new(StripeApplePayPredecrypt { diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 04f634262cfd..3990fc9c7e47 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -856,12 +856,12 @@ impl ApplePay for payments::ApplePayWalletData { } pub trait ApplePayDecrypt { - fn get_applepay_decrypted_expiration_month(&self) -> Result, Error>; - fn get_applepay_decrypted_expiration_year_4_digit(&self) -> Result, Error>; + fn get_expiry_month(&self) -> Result, Error>; + fn get_four_digit_expiry_year(&self) -> Result, Error>; } impl ApplePayDecrypt for Box { - fn get_applepay_decrypted_expiration_year_4_digit(&self) -> Result, Error> { + fn get_four_digit_expiry_year(&self) -> Result, Error> { Ok(Secret::new(format!( "20{}", self.application_expiration_date @@ -871,7 +871,7 @@ impl ApplePayDecrypt for Box { ))) } - fn get_applepay_decrypted_expiration_month(&self) -> Result, Error> { + fn get_expiry_month(&self) -> Result, Error> { Ok(Secret::new( self.application_expiration_date .peek()