From 63971d3d1500efcebc81cc184ddcb222d6e5dcb5 Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL Date: Fri, 15 Dec 2023 15:55:41 +0530 Subject: [PATCH 1/2] feat(connector): [CYBERSOURCE] Implement Google Pay --- crates/router/src/configs/defaults.rs | 87 +++++ .../src/connector/cybersource/transformers.rs | 347 +++++++++++++----- 2 files changed, 350 insertions(+), 84 deletions(-) diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 4abb878043fd..80e98a68f5d0 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -4809,6 +4809,93 @@ impl Default for super::settings::RequiredFields { ), common: HashMap::new(), } + ), + ( + enums::Connector::Cybersource, + 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(), + } ) ]), }, diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index a4cea13e2184..6a450f8d031a 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -1,12 +1,13 @@ use api_models::payments; +use base64::Engine; use common_utils::pii; use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ - self, AddressDetailsData, PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, - RouterData, + self, AddressDetailsData, CardData, PaymentsAuthorizeRequestData, + PaymentsSetupMandateRequestData, RouterData, }, consts, core::errors, @@ -66,7 +67,7 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { let order_information = OrderInformationWithBill { amount_details: Amount { total_amount: "0".to_string(), - currency: item.request.currency.to_string(), + currency: item.request.currency, }, bill_to: Some(bill_to), }; @@ -90,6 +91,7 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { action_token_types, authorization_options, commerce_indicator: CybersourceCommerceIndicator::Internet, + payment_solution: None, }; let client_reference_information = ClientReferenceInformation { @@ -103,11 +105,12 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { expiration_month: ccard.card_exp_month, expiration_year: ccard.card_exp_year, security_code: ccard.card_cvc, + card_type: None, }); - PaymentInformation { + PaymentInformation::Cards(CardPaymentInformation { card, instrument_identifier: None, - } + }) } _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), @@ -140,6 +143,7 @@ pub struct ProcessingInformation { commerce_indicator: CybersourceCommerceIndicator, capture: Option, capture_options: Option, + payment_solution: Option, } #[derive(Debug, Serialize)] @@ -197,11 +201,30 @@ pub struct CaptureOptions { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct PaymentInformation { +pub struct CardPaymentInformation { card: CardDetails, instrument_identifier: Option, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FluidData { + value: Secret, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayPaymentInformation { + fluid_data: FluidData, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum PaymentInformation { + Cards(CardPaymentInformation), + GooglePay(GooglePayPaymentInformation), +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CybersoucreInstrumentIdentifier { id: String, @@ -221,6 +244,8 @@ pub struct Card { expiration_month: Secret, expiration_year: Secret, security_code: Secret, + #[serde(rename = "type")] + card_type: Option, } #[derive(Debug, Serialize)] @@ -253,7 +278,7 @@ pub struct OrderInformation { #[serde(rename_all = "camelCase")] pub struct Amount { total_amount: String, - currency: String, + currency: api_models::enums::Currency, } #[derive(Debug, Serialize)] @@ -263,6 +288,22 @@ pub struct AdditionalAmount { currency: String, } +#[derive(Debug, Serialize)] +pub enum PaymentSolution { + ApplePay, + GooglePay, +} + +impl From for String { + fn from(solution: PaymentSolution) -> Self { + let payment_solution = match solution { + PaymentSolution::ApplePay => "001", + PaymentSolution::GooglePay => "012", + }; + payment_solution.to_string() + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BillTo { @@ -276,6 +317,82 @@ pub struct BillTo { email: pii::Email, } +impl From<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> + for ClientReferenceInformation +{ + fn from(item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>) -> Self { + Self { + code: Some(item.router_data.connector_request_reference_id.clone()), + } + } +} + +impl + From<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Option, + )> for ProcessingInformation +{ + fn from( + (item, solution): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Option, + ), + ) -> Self { + let (action_list, action_token_types, authorization_options) = + if item.router_data.request.setup_future_usage.is_some() { + ( + Some(vec![CybersourceActionsList::TokenCreate]), + Some(vec![CybersourceActionsTokenType::InstrumentIdentifier]), + Some(CybersourceAuthorizationOptions { + initiator: CybersourcePaymentInitiator { + initiator_type: Some(CybersourcePaymentInitiatorTypes::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: CybersourceCommerceIndicator::Internet, + } + } +} + +impl + From<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + BillTo, + )> for OrderInformationWithBill +{ + fn from( + (item, bill_to): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + BillTo, + ), + ) -> Self { + Self { + amount_details: Amount { + total_amount: item.amount.to_owned(), + currency: item.router_data.request.currency, + }, + bill_to: Some(bill_to), + } + } +} + // for cybersource each item in Billing is mandatory fn build_bill_to( address_details: &payments::Address, @@ -297,85 +414,150 @@ fn build_bill_to( }) } -impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> - for CybersourcePaymentsRequest +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + payments::Card, + )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( - item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + (item, ccard): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + payments::Card, + ), ) -> 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 { - amount_details: Amount { - total_amount: item.amount.to_owned(), - currency: item.router_data.request.currency.to_string(), - }, - bill_to: Some(bill_to), + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, }; - let (action_list, action_token_types, authorization_options) = - if item.router_data.request.setup_future_usage.is_some() { - ( - Some(vec![CybersourceActionsList::TokenCreate]), - Some(vec![CybersourceActionsTokenType::InstrumentIdentifier]), - Some(CybersourceAuthorizationOptions { - initiator: CybersourcePaymentInitiator { - initiator_type: Some(CybersourcePaymentInitiatorTypes::Customer), - credential_stored_on_file: Some(true), - stored_credential_used: None, - }, - merchant_intitiated_transaction: None, - }), - ) - } else { - (None, None, None) - }; - let processing_information = ProcessingInformation { - capture: Some(matches!( - item.router_data.request.capture_method, - Some(enums::CaptureMethod::Automatic) | None - )), - capture_options: None, - action_list, - action_token_types, - authorization_options, - commerce_indicator: CybersourceCommerceIndicator::Internet, - }; + let instrument_identifier = + item.router_data + .request + .connector_mandate_id() + .map(|mandate_token_id| CybersoucreInstrumentIdentifier { + id: mandate_token_id, + }); - let client_reference_information = ClientReferenceInformation { - code: Some(item.router_data.connector_request_reference_id.clone()), + let card = if instrument_identifier.is_some() { + CardDetails::MandateCard(MandateCardDetails { + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + }) + } else { + CardDetails::PaymentCard(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 = match item.router_data.request.payment_method_data.clone() { - api::PaymentMethodData::Card(ccard) => { - let instrument_identifier = - item.router_data - .request - .connector_mandate_id() - .map(|mandate_token_id| CybersoucreInstrumentIdentifier { - id: mandate_token_id, - }); - let card = if instrument_identifier.is_some() { - CardDetails::MandateCard(MandateCardDetails { - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - }) - } else { - CardDetails::PaymentCard(Card { - number: ccard.card_number, - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - security_code: ccard.card_cvc, - }) - }; - PaymentInformation { - card, - instrument_identifier, + + let payment_information = PaymentInformation::Cards(CardPaymentInformation { + card, + instrument_identifier, + }); + + let processing_information = ProcessingInformation::from((item, None)); + let client_reference_information = ClientReferenceInformation::from(item); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + }) + } +} + +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + payments::GooglePayWalletData, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, google_pay_data): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + payments::GooglePayWalletData, + ), + ) -> 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 processing_information = + ProcessingInformation::from((item, Some(PaymentSolution::GooglePay))); + let client_reference_information = ClientReferenceInformation::from(item); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + }) + } +} + +impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> + for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &CybersourceRouterData<&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::GooglePay(google_pay_data) => { + Self::try_from((item, google_pay_data)) } - } + payments::WalletData::ApplePay(_) + | 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::Wallet(_) | payments::PaymentMethodData::PayLater(_) | payments::PaymentMethodData::BankRedirect(_) | payments::PaymentMethodData::BankDebit(_) @@ -388,16 +570,11 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> | payments::PaymentMethodData::GiftCard(_) | payments::PaymentMethodData::CardToken(_) => { Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Cybersource"), - ))? + utils::get_unimplemented_payment_method_error_message("Bank of America"), + ) + .into()) } - }; - Ok(Self { - processing_information, - payment_information, - order_information, - client_reference_information, - }) + } } } @@ -433,11 +610,12 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsCaptureRouterData>> authorization_options: None, capture: None, commerce_indicator: CybersourceCommerceIndicator::Internet, + payment_solution: None, }, order_information: OrderInformationWithBill { amount_details: Amount { total_amount: item.amount.clone(), - currency: item.router_data.request.currency.to_string(), + currency: item.router_data.request.currency, }, bill_to: None, }, @@ -469,6 +647,7 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRout commerce_indicator: CybersourceCommerceIndicator::Internet, capture: None, capture_options: None, + payment_solution: None, }, order_information: OrderInformationIncrementalAuthorization { amount_details: AdditionalAmount { @@ -917,7 +1096,7 @@ impl TryFrom<&CybersourceRouterData<&types::RefundsRouterData>> for Cybers order_information: OrderInformation { amount_details: Amount { total_amount: item.amount.clone(), - currency: item.router_data.request.currency.to_string(), + currency: item.router_data.request.currency, }, }, }) From e8e402ae96565780b209adcc4f2d7b8383ebb3c0 Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL Date: Fri, 15 Dec 2023 15:59:09 +0530 Subject: [PATCH 2/2] feat(connector): [CYBERSOURCE] Implement Google Pay --- crates/router/src/connector/cybersource/transformers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index 6a450f8d031a..a5b55f111b9e 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -553,7 +553,7 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> | payments::WalletData::WeChatPayQr(_) | payments::WalletData::CashappQr(_) | payments::WalletData::SwishQr(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Bank of America"), + utils::get_unimplemented_payment_method_error_message("Cybersource"), ) .into()), }, @@ -570,7 +570,7 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> | payments::PaymentMethodData::GiftCard(_) | payments::PaymentMethodData::CardToken(_) => { Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Bank of America"), + utils::get_unimplemented_payment_method_error_message("Cybersource"), ) .into()) }