diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index a3e67c1d1c..64893d291c 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -7945,6 +7945,7 @@ "trustpay", "tsys", "volt", + "wellsfargo", "wise", "worldline", "worldpay", @@ -20130,6 +20131,7 @@ "trustpay", "tsys", "volt", + "wellsfargo", "wise", "worldline", "worldpay", diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index f040537673..01a428a12f 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -135,7 +135,7 @@ pub enum Connector { // Tsys, Tsys, Volt, - // Wellsfargo, + Wellsfargo, Wise, Worldline, Worldpay, @@ -255,7 +255,7 @@ impl Connector { | Self::Trustpay | Self::Tsys | Self::Volt - // | Self::Wellsfargo + | Self::Wellsfargo | Self::Wise | Self::Worldline | Self::Worldpay diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 6ec400ec81..f80dc9698d 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -248,7 +248,7 @@ pub enum RoutableConnectors { // Tsys, Tsys, Volt, - // Wellsfargo, + Wellsfargo, Wise, Worldline, Worldpay, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index bc781e0529..6e9e4f3bcc 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -199,6 +199,7 @@ pub struct ConnectorConfig { pub netcetera: Option, pub tsys: Option, pub volt: Option, + pub wellsfargo: Option, #[cfg(feature = "payouts")] pub wise_payout: Option, pub worldline: Option, @@ -331,6 +332,7 @@ impl ConnectorConfig { Connector::Threedsecureio => Ok(connector_data.threedsecureio), Connector::Tsys => Ok(connector_data.tsys), Connector::Volt => Ok(connector_data.volt), + Connector::Wellsfargo => Ok(connector_data.wellsfargo), Connector::Wise => Err("Use get_payout_connector_config".to_string()), Connector::Worldline => Ok(connector_data.worldline), Connector::Worldpay => Ok(connector_data.worldpay), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 331d9ba744..3134294635 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -3794,4 +3794,50 @@ key1="Public Api Key" payment_method_type = "UnionPay" [datatrans.connector_auth.BodyKey] api_key = "Passcode" -key1 = "datatrans MerchantId" \ No newline at end of file +key1 = "datatrans MerchantId" + +[wellsfargo] +[[wellsfargo.credit]] + payment_method_type = "Mastercard" +[[wellsfargo.credit]] + payment_method_type = "Visa" +[[wellsfargo.credit]] + payment_method_type = "Interac" +[[wellsfargo.credit]] + payment_method_type = "AmericanExpress" +[[wellsfargo.credit]] + payment_method_type = "JCB" +[[wellsfargo.credit]] + payment_method_type = "DinersClub" +[[wellsfargo.credit]] + payment_method_type = "Discover" +[[wellsfargo.credit]] + payment_method_type = "CartesBancaires" +[[wellsfargo.credit]] + payment_method_type = "UnionPay" +[[wellsfargo.debit]] + payment_method_type = "Mastercard" +[[wellsfargo.debit]] + payment_method_type = "Visa" +[[wellsfargo.debit]] + payment_method_type = "Interac" +[[wellsfargo.debit]] + payment_method_type = "AmericanExpress" +[[wellsfargo.debit]] + payment_method_type = "JCB" +[[wellsfargo.debit]] + payment_method_type = "DinersClub" +[[wellsfargo.debit]] + payment_method_type = "Discover" +[[wellsfargo.debit]] + payment_method_type = "CartesBancaires" +[[wellsfargo.debit]] + payment_method_type = "UnionPay" +[[wellsfargo.wallet]] + payment_method_type = "apple_pay" +[[wellsfargo.wallet]] + payment_method_type = "google_pay" +[wellsfargo.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" \ No newline at end of file diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index cfdc2e4b63..6af90f71f5 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -2712,4 +2712,50 @@ key1="Public Api Key" payment_method_type = "UnionPay" [datatrans.connector_auth.BodyKey] api_key = "Passcode" -key1 = "datatrans MerchantId" \ No newline at end of file +key1 = "datatrans MerchantId" + +[wellsfargo] +[[wellsfargo.credit]] + payment_method_type = "Mastercard" +[[wellsfargo.credit]] + payment_method_type = "Visa" +[[wellsfargo.credit]] + payment_method_type = "Interac" +[[wellsfargo.credit]] + payment_method_type = "AmericanExpress" +[[wellsfargo.credit]] + payment_method_type = "JCB" +[[wellsfargo.credit]] + payment_method_type = "DinersClub" +[[wellsfargo.credit]] + payment_method_type = "Discover" +[[wellsfargo.credit]] + payment_method_type = "CartesBancaires" +[[wellsfargo.credit]] + payment_method_type = "UnionPay" +[[wellsfargo.debit]] + payment_method_type = "Mastercard" +[[wellsfargo.debit]] + payment_method_type = "Visa" +[[wellsfargo.debit]] + payment_method_type = "Interac" +[[wellsfargo.debit]] + payment_method_type = "AmericanExpress" +[[wellsfargo.debit]] + payment_method_type = "JCB" +[[wellsfargo.debit]] + payment_method_type = "DinersClub" +[[wellsfargo.debit]] + payment_method_type = "Discover" +[[wellsfargo.debit]] + payment_method_type = "CartesBancaires" +[[wellsfargo.debit]] + payment_method_type = "UnionPay" +[[wellsfargo.wallet]] + payment_method_type = "apple_pay" +[[wellsfargo.wallet]] + payment_method_type = "google_pay" +[wellsfargo.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" \ No newline at end of file diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index ca5f6fb72e..f522fa7278 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -3787,4 +3787,50 @@ key1="Public Api Key" payment_method_type = "UnionPay" [datatrans.connector_auth.BodyKey] api_key = "Passcode" -key1 = "datatrans MerchantId" \ No newline at end of file +key1 = "datatrans MerchantId" + +[wellsfargo] +[[wellsfargo.credit]] + payment_method_type = "Mastercard" +[[wellsfargo.credit]] + payment_method_type = "Visa" +[[wellsfargo.credit]] + payment_method_type = "Interac" +[[wellsfargo.credit]] + payment_method_type = "AmericanExpress" +[[wellsfargo.credit]] + payment_method_type = "JCB" +[[wellsfargo.credit]] + payment_method_type = "DinersClub" +[[wellsfargo.credit]] + payment_method_type = "Discover" +[[wellsfargo.credit]] + payment_method_type = "CartesBancaires" +[[wellsfargo.credit]] + payment_method_type = "UnionPay" +[[wellsfargo.debit]] + payment_method_type = "Mastercard" +[[wellsfargo.debit]] + payment_method_type = "Visa" +[[wellsfargo.debit]] + payment_method_type = "Interac" +[[wellsfargo.debit]] + payment_method_type = "AmericanExpress" +[[wellsfargo.debit]] + payment_method_type = "JCB" +[[wellsfargo.debit]] + payment_method_type = "DinersClub" +[[wellsfargo.debit]] + payment_method_type = "Discover" +[[wellsfargo.debit]] + payment_method_type = "CartesBancaires" +[[wellsfargo.debit]] + payment_method_type = "UnionPay" +[[wellsfargo.wallet]] + payment_method_type = "apple_pay" +[[wellsfargo.wallet]] + payment_method_type = "google_pay" +[wellsfargo.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" \ No newline at end of file diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index be29da50ae..c34c00599a 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -167,6 +167,7 @@ impl Default for Mandates { enums::Connector::Bankofamerica, enums::Connector::Noon, enums::Connector::Cybersource, + enums::Connector::Wellsfargo, ]), }, ), @@ -178,6 +179,7 @@ impl Default for Mandates { enums::Connector::Adyen, enums::Connector::Bankofamerica, enums::Connector::Cybersource, + enums::Connector::Wellsfargo, ]), }, ), @@ -202,6 +204,7 @@ impl Default for Mandates { enums::Connector::Stripe, enums::Connector::Bankofamerica, enums::Connector::Cybersource, + enums::Connector::Wellsfargo, ]), }, ), @@ -2769,6 +2772,129 @@ impl Default for super::settings::RequiredFields { common: HashMap::new() } ), + ( + enums::Connector::Wellsfargo, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common:HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + } + ), ( enums::Connector::Worldline, RequiredFieldFinal { @@ -5444,6 +5570,129 @@ impl Default for super::settings::RequiredFields { common: HashMap::new() } ), + ( + enums::Connector::Wellsfargo, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + } + ), ( enums::Connector::Worldline, RequiredFieldFinal { @@ -7320,6 +7569,160 @@ impl Default for super::settings::RequiredFields { ), common: HashMap::new(), } + ), + ( + enums::Connector::Wellsfargo, + 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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } ) ]), }, @@ -7886,6 +8289,160 @@ impl Default for super::settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Wellsfargo, + 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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.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: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ]), }, ), diff --git a/crates/router/src/connector/wellsfargo.rs b/crates/router/src/connector/wellsfargo.rs index 51ff838afd..f5cdb6431b 100644 --- a/crates/router/src/connector/wellsfargo.rs +++ b/crates/router/src/connector/wellsfargo.rs @@ -1,13 +1,24 @@ pub mod transformers; -use common_utils::types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}; -use error_stack::{report, ResultExt}; -use masking::ExposeInterface; -use transformers as wellsfargo; +use std::fmt::Debug; + +use base64::Engine; +use common_utils::request::RequestContent; +use diesel_models::enums; +use error_stack::{report, Report, ResultExt}; +use masking::{ExposeInterface, PeekInterface}; +use ring::{digest, hmac}; +use time::OffsetDateTime; +use transformers as wellsfargo; +use url::Url; -use super::utils::{self as connector_utils}; use crate::{ configs::settings, + connector::{ + utils as connector_utils, + utils::{PaymentMethodDataType, RefundsRequestData}, + }, + consts, core::errors::{self, CustomResult}, events::connector_api_logs::ConnectorEvent, headers, @@ -19,36 +30,313 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, RequestContent, Response, + transformers::ForeignTryFrom, }, utils::BytesExt, }; -#[derive(Clone)] -pub struct Wellsfargo { - amount_converter: &'static (dyn AmountConvertor + Sync), -} +#[derive(Debug, Clone)] +pub struct Wellsfargo; impl Wellsfargo { - pub fn new() -> &'static Self { - &Self { - amount_converter: &StringMinorUnitForConnector, + pub fn generate_digest(&self, payload: &[u8]) -> String { + let payload_digest = digest::digest(&digest::SHA256, payload); + consts::BASE64_ENGINE.encode(payload_digest) + } + + pub fn generate_signature( + &self, + auth: wellsfargo::WellsfargoAuthType, + host: String, + resource: &str, + payload: &String, + date: OffsetDateTime, + http_method: services::Method, + ) -> CustomResult { + let wellsfargo::WellsfargoAuthType { + api_key, + merchant_account, + api_secret, + } = auth; + let is_post_method = matches!(http_method, services::Method::Post); + let is_patch_method = matches!(http_method, services::Method::Patch); + let is_delete_method = matches!(http_method, services::Method::Delete); + let digest_str = if is_post_method || is_patch_method { + "digest " + } else { + "" + }; + let headers = format!("host date (request-target) {digest_str}v-c-merchant-id"); + let request_target = if is_post_method { + format!("(request-target): post {resource}\ndigest: SHA-256={payload}\n") + } else if is_patch_method { + format!("(request-target): patch {resource}\ndigest: SHA-256={payload}\n") + } else if is_delete_method { + format!("(request-target): delete {resource}\n") + } else { + format!("(request-target): get {resource}\n") + }; + let signature_string = format!( + "host: {host}\ndate: {date}\n{request_target}v-c-merchant-id: {}", + merchant_account.peek() + ); + let key_value = consts::BASE64_ENGINE + .decode(api_secret.expose()) + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "connector_account_details.api_secret", + })?; + let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value); + let signature_value = + consts::BASE64_ENGINE.encode(hmac::sign(&key, signature_string.as_bytes()).as_ref()); + let signature_header = format!( + r#"keyid="{}", algorithm="HmacSHA256", headers="{headers}", signature="{signature_value}""#, + api_key.peek() + ); + + Ok(signature_header) + } +} + +impl ConnectorCommon for Wellsfargo { + fn id(&self) -> &'static str { + "wellsfargo" + } + + fn common_get_content_type(&self) -> &'static str { + "application/json;charset=utf-8" + } + + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.wellsfargo.base_url.as_ref() + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + + fn build_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: Result< + wellsfargo::WellsfargoErrorResponse, + Report, + > = res.response.parse_struct("Wellsfargo ErrorResponse"); + + let error_message = if res.status_code == 401 { + consts::CONNECTOR_UNAUTHORIZED_ERROR + } else { + consts::NO_ERROR_MESSAGE + }; + match response { + Ok(transformers::WellsfargoErrorResponse::StandardError(response)) => { + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + let (code, message, reason) = match response.error_information { + Some(ref error_info) => { + let detailed_error_info = error_info.details.as_ref().map(|details| { + details + .iter() + .map(|det| format!("{} : {}", det.field, det.reason)) + .collect::>() + .join(", ") + }); + ( + error_info.reason.clone(), + error_info.reason.clone(), + transformers::get_error_reason( + Some(error_info.message.clone()), + detailed_error_info, + None, + ), + ) + } + None => { + let detailed_error_info = response.details.map(|details| { + details + .iter() + .map(|det| format!("{} : {}", det.field, det.reason)) + .collect::>() + .join(", ") + }); + ( + response + .reason + .clone() + .map_or(consts::NO_ERROR_CODE.to_string(), |reason| { + reason.to_string() + }), + response + .reason + .map_or(error_message.to_string(), |reason| reason.to_string()), + transformers::get_error_reason( + response.message, + detailed_error_info, + None, + ), + ) + } + }; + + Ok(types::ErrorResponse { + status_code: res.status_code, + code, + message, + reason, + attempt_status: None, + connector_transaction_id: None, + }) + } + Ok(transformers::WellsfargoErrorResponse::AuthenticationError(response)) => { + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + Ok(types::ErrorResponse { + status_code: res.status_code, + code: consts::NO_ERROR_CODE.to_string(), + message: response.response.rmsg.clone(), + reason: Some(response.response.rmsg), + attempt_status: None, + connector_transaction_id: None, + }) + } + Ok(transformers::WellsfargoErrorResponse::NotAvailableError(response)) => { + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + let error_response = response + .errors + .iter() + .map(|error_info| { + format!( + "{}: {}", + error_info.error_type.clone().unwrap_or("".to_string()), + error_info.message.clone().unwrap_or("".to_string()) + ) + }) + .collect::>() + .join(" & "); + Ok(types::ErrorResponse { + status_code: res.status_code, + code: consts::NO_ERROR_CODE.to_string(), + message: error_response.clone(), + reason: Some(error_response), + attempt_status: None, + connector_transaction_id: None, + }) + } + Err(error_msg) => { + event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); + router_env::logger::error!(deserialization_error =? error_msg); + crate::utils::handle_json_response_deserialization_failure(res, "wellsfargo") + } } } } +impl ConnectorValidation for Wellsfargo { + fn validate_capture_method( + &self, + capture_method: Option, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( + connector_utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: types::domain::payments::PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd = std::collections::HashSet::from([ + PaymentMethodDataType::Card, + PaymentMethodDataType::ApplePay, + PaymentMethodDataType::GooglePay, + ]); + connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + } +} + +impl ConnectorCommonExt for Wellsfargo +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let date = OffsetDateTime::now_utc(); + let wellsfargo_req = self.get_request_body(req, connectors)?; + let auth = wellsfargo::WellsfargoAuthType::try_from(&req.connector_auth_type)?; + let merchant_account = auth.merchant_account.clone(); + let base_url = connectors.wellsfargo.base_url.as_str(); + let wellsfargo_host = + Url::parse(base_url).change_context(errors::ConnectorError::RequestEncodingFailed)?; + let host = wellsfargo_host + .host_str() + .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + let path: String = self + .get_url(req, connectors)? + .chars() + .skip(base_url.len() - 1) + .collect(); + let sha256 = self.generate_digest(wellsfargo_req.get_inner_value().expose().as_bytes()); + let http_method = self.get_http_method(); + let signature = self.generate_signature( + auth, + host.to_string(), + path.as_str(), + &sha256, + date, + http_method, + )?; + + let mut headers = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::ACCEPT.to_string(), + "application/hal+json;charset=utf-8".to_string().into(), + ), + ( + "v-c-merchant-id".to_string(), + merchant_account.into_masked(), + ), + ("Date".to_string(), date.to_string().into()), + ("Host".to_string(), host.to_string().into()), + ("Signature".to_string(), signature.into_masked()), + ]; + if matches!( + http_method, + services::Method::Post | services::Method::Put | services::Method::Patch + ) { + headers.push(( + "Digest".to_string(), + format!("SHA-256={sha256}").into_masked(), + )); + } + Ok(headers) + } +} + impl api::Payment for Wellsfargo {} -impl api::PaymentSession for Wellsfargo {} -impl api::ConnectorAccessToken for Wellsfargo {} -impl api::MandateSetup for Wellsfargo {} impl api::PaymentAuthorize for Wellsfargo {} impl api::PaymentSync for Wellsfargo {} -impl api::PaymentCapture for Wellsfargo {} impl api::PaymentVoid for Wellsfargo {} -impl api::Refund for Wellsfargo {} -impl api::RefundExecute for Wellsfargo {} -impl api::RefundSync for Wellsfargo {} +impl api::PaymentCapture for Wellsfargo {} +impl api::PaymentIncrementalAuthorization for Wellsfargo {} +impl api::MandateSetup for Wellsfargo {} +impl api::ConnectorAccessToken for Wellsfargo {} impl api::PaymentToken for Wellsfargo {} +impl api::ConnectorMandateRevoke for Wellsfargo {} impl ConnectorIntegration< @@ -60,96 +348,6 @@ impl // Not Implemented (R) } -impl ConnectorCommonExt for Wellsfargo -where - Self: ConnectorIntegration, -{ - fn build_headers( - &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); - Ok(header) - } -} - -impl ConnectorCommon for Wellsfargo { - fn id(&self) -> &'static str { - "wellsfargo" - } - - fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Minor - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base - } - - fn common_get_content_type(&self) -> &'static str { - "application/json" - } - - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { - connectors.wellsfargo.base_url.as_ref() - } - - fn get_auth_header( - &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { - let auth = wellsfargo::WellsfargoAuthType::try_from(auth_type) - .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), - )]) - } - - fn build_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - let response: wellsfargo::WellsfargoErrorResponse = res - .response - .parse_struct("WellsfargoErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - - Ok(ErrorResponse { - status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, - attempt_status: None, - connector_transaction_id: None, - }) - } -} - -impl ConnectorValidation for Wellsfargo { - //TODO: implement functions when support enabled -} - -impl ConnectorIntegration - for Wellsfargo -{ - //TODO: implement sessions flow -} - -impl ConnectorIntegration - for Wellsfargo -{ -} - impl ConnectorIntegration< api::SetupMandate, @@ -157,6 +355,410 @@ impl types::PaymentsResponseData, > for Wellsfargo { + 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 = wellsfargo::WellsfargoZeroMandateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + 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: types::Response, + ) -> CustomResult { + let response: wellsfargo::WellsfargoPaymentsResponse = res + .response + .parse_struct("WellsfargoSetupMandatesResponse") + .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: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: wellsfargo::WellsfargoServerErrorResponse = res + .response + .parse_struct("WellsfargoServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|event| event.set_response_body(&response)); + router_env::logger::info!(error_response=?response); + + 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(types::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, + }) + } +} + +impl + ConnectorIntegration< + api::MandateRevoke, + types::MandateRevokeRequestData, + types::MandateRevokeResponseData, + > for Wellsfargo +{ + fn get_headers( + &self, + req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_http_method(&self) -> services::Method { + services::Method::Delete + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}tms/v1/paymentinstruments/{}", + self.base_url(connectors), + connector_utils::RevokeMandateRequestData::get_connector_mandate_id(&req.request)? + )) + } + fn build_request( + &self, + req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Delete) + .url(&types::MandateRevokeType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::MandateRevokeType::get_headers( + self, req, connectors, + )?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::MandateRevokeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: types::Response, + ) -> CustomResult { + if matches!(res.status_code, 204) { + event_builder.map(|i| i.set_response_body(&serde_json::json!({"mandate_status": common_enums::MandateStatus::Revoked.to_string()}))); + Ok(types::MandateRevokeRouterData { + response: Ok(types::MandateRevokeResponseData { + mandate_status: common_enums::MandateStatus::Revoked, + }), + ..data.clone() + }) + } else { + // If http_code != 204 || http_code != 4xx, we dont know any other response scenario yet. + let response_value: serde_json::Value = serde_json::from_slice(&res.response) + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + let response_string = response_value.to_string(); + + event_builder.map(|i| { + i.set_response_body( + &serde_json::json!({"response_string": response_string.clone()}), + ) + }); + router_env::logger::info!(connector_response=?response_string); + + Ok(types::MandateRevokeRouterData { + response: Err(types::ErrorResponse { + code: consts::NO_ERROR_CODE.to_string(), + message: response_string.clone(), + reason: Some(response_string), + status_code: res.status_code, + attempt_status: None, + connector_transaction_id: None, + }), + ..data.clone() + }) + } + } + fn get_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} +impl ConnectorIntegration + for Wellsfargo +{ + // Not Implemented (R) +} + +impl api::PaymentSession for Wellsfargo {} + +impl ConnectorIntegration + for Wellsfargo +{ +} + +impl ConnectorIntegration + for Wellsfargo +{ + fn get_headers( + &self, + req: &types::PaymentsCaptureRouterData, + 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::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}pts/v2/payments/{}/captures", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_request_body( + &self, + req: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount_to_capture, + req, + ))?; + let connector_req = + wellsfargo::WellsfargoPaymentsCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: types::Response, + ) -> CustomResult< + types::RouterData, + errors::ConnectorError, + > { + let response: wellsfargo::WellsfargoPaymentsResponse = res + .response + .parse_struct("Wellsfargo PaymentResponse") + .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: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: wellsfargo::WellsfargoServerErrorResponse = res + .response + .parse_struct("WellsfargoServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|event| event.set_response_body(&response)); + router_env::logger::info!(error_response=?response); + + Ok(types::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: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorIntegration + for Wellsfargo +{ + fn get_headers( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_http_method(&self) -> services::Method { + services::Method::Get + } + + fn get_url( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}tss/v2/transactions/{}", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn build_request( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: types::Response, + ) -> CustomResult { + let response: wellsfargo::WellsfargoTransactionResponse = res + .response + .parse_struct("Wellsfargo PaymentSyncResponse") + .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: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } } impl ConnectorIntegration @@ -177,9 +779,12 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}pts/v2/payments/", + ConnectorCommon::base_url(self, connectors) + )) } fn get_request_body( @@ -187,13 +792,12 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( - self.amount_converter, - req.request.minor_amount, + let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( + &self.get_currency_unit(), req.request.currency, - )?; - - let connector_router_data = wellsfargo::WellsfargoRouterData::from((amount, req)); + req.request.amount, + req, + ))?; let connector_req = wellsfargo::WellsfargoPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -204,32 +808,30 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) - .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( - self, req, connectors, - )?) - .build(), - )) + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(self.get_request_body(req, connectors)?) + .build(); + + Ok(Some(request)) } fn handle_response( &self, data: &types::PaymentsAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: Response, + res: types::Response, ) -> CustomResult { let response: wellsfargo::WellsfargoPaymentsResponse = res .response - .parse_struct("Wellsfargo PaymentsAuthorizeResponse") + .parse_struct("Wellsfargo PaymentResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -242,230 +844,121 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } -} -impl ConnectorIntegration - for Wellsfargo -{ - fn get_headers( + fn get_5xx_error_response( &self, - req: &types::PaymentsSyncRouterData, - 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::PaymentsSyncRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn build_request( - &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .build(), - )) - } - - fn handle_response( - &self, - data: &types::PaymentsSyncRouterData, + res: types::Response, event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: wellsfargo::WellsfargoPaymentsResponse = res + ) -> CustomResult { + let response: wellsfargo::WellsfargoServerErrorResponse = res .response - .parse_struct("wellsfargo PaymentsSyncResponse") + .parse_struct("WellsfargoServerErrorResponse") .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, + + event_builder.map(|event| event.set_response_body(&response)); + router_env::logger::info!(error_response=?response); + + 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(types::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, }) } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} - -impl ConnectorIntegration - for Wellsfargo -{ - fn get_headers( - &self, - req: &types::PaymentsCaptureRouterData, - 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::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( - &self, - _req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) - } - - fn build_request( - &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &types::PaymentsCaptureRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: wellsfargo::WellsfargoPaymentsResponse = res - .response - .parse_struct("Wellsfargo PaymentsCaptureResponse") - .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) - } } impl ConnectorIntegration for Wellsfargo -{ -} - -impl ConnectorIntegration - for Wellsfargo { fn get_headers( &self, - req: &types::RefundsRouterData, + req: &types::PaymentsCancelRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } + fn get_url( + &self, + req: &types::PaymentsCancelRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}pts/v2/payments/{connector_payment_id}/reversals", + self.base_url(connectors) + )) + } + fn get_content_type(&self) -> &'static str { self.common_get_content_type() } - fn get_url( - &self, - _req: &types::RefundsRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - fn get_request_body( &self, - req: &types::RefundsRouterData, + req: &types::PaymentsCancelRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let refund_amount = connector_utils::convert_amount( - self.amount_converter, - req.request.minor_refund_amount, - req.request.currency, - )?; + let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( + &self.get_currency_unit(), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Currency", + })?, + req.request + .amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Amount", + })?, + req, + ))?; + let connector_req = wellsfargo::WellsfargoVoidRequest::try_from(&connector_router_data)?; - let connector_router_data = wellsfargo::WellsfargoRouterData::from((refund_amount, req)); - let connector_req = wellsfargo::WellsfargoRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::RefundsRouterData, + req: &types::PaymentsCancelRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) - .build(); - Ok(Some(request)) + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(self.get_request_body(req, connectors)?) + .build(), + )) } fn handle_response( &self, - data: &types::RefundsRouterData, + data: &types::PaymentsCancelRouterData, event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult, errors::ConnectorError> { - let response: wellsfargo::RefundResponse = res + res: types::Response, + ) -> CustomResult { + let response: wellsfargo::WellsfargoPaymentsResponse = res .response - .parse_struct("wellsfargo RefundResponse") + .parse_struct("Wellsfargo PaymentResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -478,9 +971,125 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: wellsfargo::WellsfargoServerErrorResponse = res + .response + .parse_struct("WellsfargoServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|event| event.set_response_body(&response)); + router_env::logger::info!(error_response=?response); + + Ok(types::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: None, + connector_transaction_id: None, + }) + } +} + +impl api::Refund for Wellsfargo {} +impl api::RefundExecute for Wellsfargo {} +impl api::RefundSync for Wellsfargo {} + +impl ConnectorIntegration + for Wellsfargo +{ + fn get_headers( + &self, + req: &types::RefundExecuteRouterData, + 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::RefundExecuteRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}pts/v2/payments/{}/refunds", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_request_body( + &self, + req: &types::RefundExecuteRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let connector_req = wellsfargo::WellsfargoRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::RefundExecuteRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(self.get_request_body(req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::RefundExecuteRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: types::Response, + ) -> CustomResult { + let response: wellsfargo::WellsfargoRefundResponse = res + .response + .parse_struct("Wellsfargo RefundResponse") + .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: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { self.build_error_response(res, event_builder) } } @@ -495,19 +1104,24 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } - fn get_content_type(&self) -> &'static str { self.common_get_content_type() } - + fn get_http_method(&self) -> services::Method { + services::Method::Get + } fn get_url( &self, - _req: &types::RefundSyncRouterData, - _connectors: &settings::Connectors, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let refund_id = req.request.get_connector_refund_id()?; + Ok(format!( + "{}tss/v2/transactions/{}", + self.base_url(connectors), + refund_id + )) } - fn build_request( &self, req: &types::RefundSyncRouterData, @@ -519,22 +1133,18 @@ impl ConnectorIntegration, - res: Response, + res: types::Response, ) -> CustomResult { - let response: wellsfargo::RefundResponse = res + let response: wellsfargo::WellsfargoRsyncResponse = res .response - .parse_struct("wellsfargo RefundSyncResponse") + .parse_struct("Wellsfargo RefundSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -544,12 +1154,123 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl + ConnectorIntegration< + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + > for Wellsfargo +{ + fn get_headers( + &self, + req: &types::PaymentsIncrementalAuthorizationRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_http_method(&self) -> services::Method { + services::Method::Patch + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &types::PaymentsIncrementalAuthorizationRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}pts/v2/payments/{}", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_request_body( + &self, + req: &types::PaymentsIncrementalAuthorizationRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.additional_amount, + req, + ))?; + let connector_request = + wellsfargo::WellsfargoPaymentsIncrementalAuthorizationRequest::try_from( + &connector_router_data, + )?; + Ok(RequestContent::Json(Box::new(connector_request))) + } + fn build_request( + &self, + req: &types::PaymentsIncrementalAuthorizationRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Patch) + .url(&types::IncrementalAuthorizationType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::IncrementalAuthorizationType::get_headers( + self, req, connectors, + )?) + .set_body(types::IncrementalAuthorizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::PaymentsIncrementalAuthorizationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: types::Response, + ) -> CustomResult< + types::RouterData< + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + >, + errors::ConnectorError, + > { + let response: wellsfargo::WellsfargoPaymentsIncrementalAuthorizationResponse = res + .response + .parse_struct("Wellsfargo PaymentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::foreign_try_from(( + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + true, + )) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + fn get_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { self.build_error_response(res, event_builder) } } @@ -559,7 +1280,7 @@ impl api::IncomingWebhook for Wellsfargo { fn get_webhook_object_reference_id( &self, _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } @@ -567,7 +1288,7 @@ impl api::IncomingWebhook for Wellsfargo { &self, _request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + Ok(api::IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( diff --git a/crates/router/src/connector/wellsfargo/transformers.rs b/crates/router/src/connector/wellsfargo/transformers.rs index d0a1bb4140..93ed04288a 100644 --- a/crates/router/src/connector/wellsfargo/transformers.rs +++ b/crates/router/src/connector/wellsfargo/transformers.rs @@ -1,43 +1,1133 @@ -use common_utils::types::StringMinorUnit; -use masking::Secret; +use api_models::payments; +use base64::Engine; +use common_enums::FutureUsage; +use common_utils::{pii, types::SemanticVersion}; +use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; +use serde_json::Value; use crate::{ - connector::utils::PaymentsAuthorizeRequestData, + connector::utils::{ + self, AddressDetailsData, ApplePayDecrypt, CardData, PaymentsAuthorizeRequestData, + PaymentsSetupMandateRequestData, PaymentsSyncRequestData, RecurringMandateData, RouterData, + }, + consts, core::errors, - types::{self, api, domain, storage::enums}, + types::{ + self, + api::{self, enums as api_enums}, + domain, + storage::enums, + transformers::{ForeignFrom, ForeignTryFrom}, + ApplePayPredecryptData, + }, + unimplemented_payment_method, }; -//TODO: Fill the struct with respective fields +#[derive(Debug, Serialize)] pub struct WellsfargoRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: String, pub router_data: T, } -impl From<(StringMinorUnit, T)> for WellsfargoRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts - Self { +impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for WellsfargoRouterData { + type Error = error_stack::Report; + fn try_from( + (currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), + ) -> Result { + // This conversion function is used at different places in the file, if updating this, keep a check for those + let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; + Ok(Self { amount, router_data: item, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoZeroMandateRequest { + processing_information: ProcessingInformation, + payment_information: PaymentInformation, + order_information: OrderInformationWithBill, + client_reference_information: ClientReferenceInformation, +} + +impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { + 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_optional_billing(), email)?; + + let order_information = OrderInformationWithBill { + amount_details: Amount { + total_amount: "0".to_string(), + currency: item.request.currency, + }, + bill_to: Some(bill_to), + }; + let (action_list, action_token_types, authorization_options) = ( + Some(vec![WellsfargoActionsList::TokenCreate]), + Some(vec![ + WellsfargoActionsTokenType::PaymentInstrument, + WellsfargoActionsTokenType::Customer, + ]), + Some(WellsfargoAuthorizationOptions { + initiator: Some(WellsfargoPaymentInitiator { + initiator_type: Some(WellsfargoPaymentInitiatorTypes::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() { + domain::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(Box::new(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: Some(ccard.card_cvc), + card_type, + }, + })), + None, + ) + } + + domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + domain::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(Box::new( + 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", "Wellsfargo"), + )?, + }, + None => ( + PaymentInformation::ApplePayToken(Box::new( + ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + descriptor: Some(FLUID_DATA_DESCRIPTOR.to_string()), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }, + )), + Some(PaymentSolution::ApplePay), + ), + } + } + domain::WalletData::GooglePay(google_pay_data) => ( + PaymentInformation::GooglePay(Box::new(GooglePayPaymentInformation { + fluid_data: FluidData { + value: Secret::from( + consts::BASE64_ENGINE + .encode(google_pay_data.tokenization_data.token), + ), + descriptor: None, + }, + })), + Some(PaymentSolution::GooglePay), + ), + domain::WalletData::AliPayQr(_) + | domain::WalletData::AliPayRedirect(_) + | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::MomoRedirect(_) + | domain::WalletData::KakaoPayRedirect(_) + | domain::WalletData::GoPayRedirect(_) + | domain::WalletData::GcashRedirect(_) + | domain::WalletData::ApplePayRedirect(_) + | domain::WalletData::ApplePayThirdPartySdk(_) + | domain::WalletData::DanaRedirect {} + | domain::WalletData::GooglePayRedirect(_) + | domain::WalletData::GooglePayThirdPartySdk(_) + | domain::WalletData::MbWayRedirect(_) + | domain::WalletData::MobilePayRedirect(_) + | domain::WalletData::PaypalRedirect(_) + | domain::WalletData::PaypalSdk(_) + | domain::WalletData::SamsungPay(_) + | domain::WalletData::TwintRedirect {} + | domain::WalletData::VippsRedirect {} + | domain::WalletData::TouchNGoRedirect(_) + | domain::WalletData::WeChatPayRedirect(_) + | domain::WalletData::WeChatPayQr(_) + | domain::WalletData::CashappQr(_) + | domain::WalletData::SwishQr(_) + | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wellsfargo"), + ))?, + }, + 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::RealTimePayment(_) + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::OpenBanking(_) + | domain::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wellsfargo"), + ))? + } + }; + + 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, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoPaymentsRequest { + processing_information: ProcessingInformation, + payment_information: PaymentInformation, + order_information: OrderInformationWithBill, + client_reference_information: ClientReferenceInformation, + #[serde(skip_serializing_if = "Option::is_none")] + consumer_authentication_information: Option, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, +} + +#[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, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoConsumerAuthInformation { + ucaf_collection_indicator: Option, + cavv: Option, + ucaf_authentication_data: Option>, + xid: Option, + directory_server_transaction_id: Option>, + specification_version: Option, + /// This field specifies the 3ds version + pa_specification_version: Option, + /// Verification response enrollment status. + /// + /// This field is supported only on Asia, Middle East, and Africa Gateway. + /// + /// For external authentication, this field will always be "Y" + veres_enrolled: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantDefinedInformation { + key: u8, + value: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum WellsfargoActionsList { + TokenCreate, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum WellsfargoActionsTokenType { + Customer, + PaymentInstrument, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoAuthorizationOptions { + initiator: Option, + merchant_intitiated_transaction: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantInitiatedTransaction { + reason: Option, + previous_transaction_id: Option>, + //Required for recurring mandates payment + original_authorized_amount: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoPaymentInitiator { + #[serde(rename = "type")] + initiator_type: Option, + credential_stored_on_file: Option, + stored_credential_used: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum WellsfargoPaymentInitiatorTypes { + Customer, + Merchant, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CaptureOptions { + capture_sequence_number: u32, + total_capture_count: u32, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CardPaymentInformation { + card: Card, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenizedCard { + number: Secret, + expiration_month: Secret, + expiration_year: Secret, + cryptogram: Secret, + transaction_type: TransactionType, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayTokenizedCard { + transaction_type: TransactionType, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayTokenPaymentInformation { + fluid_data: FluidData, + tokenized_card: ApplePayTokenizedCard, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayPaymentInformation { + tokenized_card: TokenizedCard, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MandatePaymentInformation { + payment_instrument: WellsfargoPaymentInstrument, +} +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +struct AchBankAccount { + account: Account, + routing_number: Secret, +} + +#[derive(Debug, Deserialize, Serialize)] +struct Account { + #[serde(rename = "type")] + account_type: AccountType, + number: Secret, +} +#[derive(Debug, Deserialize, Serialize)] +enum AccountType { + /// Checking account type. + C, + /// General ledger account type. Supported only on Wells Fargo ACH. + G, + /// Savings account type. + S, + /// Corporate checking account type. + X, +} + +#[derive(Debug, Serialize)] +pub struct AchPaymentInformation { + bank: AchBankAccount, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FluidData { + value: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + descriptor: Option, +} + +pub const FLUID_DATA_DESCRIPTOR: &str = "RklEPUNPTU1PTi5BUFBMRS5JTkFQUC5QQVlNRU5U"; + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayPaymentInformation { + fluid_data: FluidData, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum PaymentInformation { + Cards(Box), + GooglePay(Box), + ApplePay(Box), + ApplePayToken(Box), + MandatePayment(Box), + AchDebitPayment(Box), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WellsfargoPaymentInstrument { + id: Secret, +} +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Card { + number: cards::CardNumber, + expiration_month: Secret, + expiration_year: Secret, + security_code: Option>, + #[serde(rename = "type")] + card_type: Option, +} +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OrderInformationWithBill { + amount_details: Amount, + bill_to: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OrderInformationIncrementalAuthorization { + amount_details: AdditionalAmount, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OrderInformation { + amount_details: Amount, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Amount { + total_amount: String, + currency: api_models::enums::Currency, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalAmount { + additional_amount: String, + currency: String, +} + +#[derive(Debug, Serialize)] +pub enum PaymentSolution { + ApplePay, + GooglePay, +} + +#[derive(Debug, Serialize)] +pub enum TransactionType { + #[serde(rename = "1")] + ApplePay, +} + +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 { + first_name: Option>, + last_name: Option>, + address1: Option>, + locality: Option, + #[serde(skip_serializing_if = "Option::is_none")] + administrative_area: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + postal_code: Option>, + country: Option, + email: pii::Email, + phone_number: Option>, +} + +impl From<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> + for ClientReferenceInformation +{ + fn from(item: &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>) -> Self { + Self { + code: Some(item.router_data.connector_request_reference_id.clone()), } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct WellsfargoPaymentsRequest { - amount: StringMinorUnit, - card: WellsfargoCard, +impl + TryFrom<( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + Option, + Option, + )> for ProcessingInformation +{ + type Error = error_stack::Report; + fn try_from( + (item, solution, network): ( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + Option, + Option, + ), + ) -> Result { + let mut commerce_indicator = solution + .as_ref() + .map(|pm_solution| match pm_solution { + PaymentSolution::ApplePay => network + .as_ref() + .map(|card_network| match card_network.to_lowercase().as_str() { + "amex" => "aesk", + "discover" => "dipb", + "mastercard" => "spa", + "visa" => "internet", + _ => "internet", + }) + .unwrap_or("internet"), + PaymentSolution::GooglePay => "internet", + }) + .unwrap_or("internet") + .to_string(); + + 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) + }) + && (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![WellsfargoActionsList::TokenCreate]), + Some(vec![ + WellsfargoActionsTokenType::PaymentInstrument, + WellsfargoActionsTokenType::Customer, + ]), + Some(WellsfargoAuthorizationOptions { + initiator: Some(WellsfargoPaymentInitiator { + initiator_type: Some(WellsfargoPaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, + }), + merchant_intitiated_transaction: None, + }), + ) + } else if item.router_data.request.mandate_id.is_some() { + match item + .router_data + .request + .mandate_id + .clone() + .and_then(|mandate_id| mandate_id.mandate_reference_id) + { + Some(payments::MandateReferenceId::ConnectorMandateId(_)) => { + 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(WellsfargoAuthorizationOptions { + initiator: None, + merchant_intitiated_transaction: Some(MerchantInitiatedTransaction { + reason: None, + original_authorized_amount: Some(utils::get_amount_as_string( + &api::CurrencyUnit::Base, + original_amount, + original_currency, + )?), + previous_transaction_id: None, + }), + }), + ) + } + Some(payments::MandateReferenceId::NetworkMandateId(network_transaction_id)) => { + let (original_amount, original_currency) = match network + .clone() + .map(|network| network.to_lowercase()) + .as_deref() + { + Some("discover") => { + let original_amount = Some( + item.router_data + .get_recurring_mandate_payment_data()? + .get_original_payment_amount()?, + ); + let original_currency = Some( + item.router_data + .get_recurring_mandate_payment_data()? + .get_original_payment_currency()?, + ); + (original_amount, original_currency) + } + _ => { + let original_amount = item + .router_data + .recurring_mandate_payment_data + .as_ref() + .and_then(|recurring_mandate_payment_data| { + recurring_mandate_payment_data + .original_payment_authorized_amount + }); + + let original_currency = item + .router_data + .recurring_mandate_payment_data + .as_ref() + .and_then(|recurring_mandate_payment_data| { + recurring_mandate_payment_data + .original_payment_authorized_currency + }); + + (original_amount, original_currency) + } + }; + + let original_authorized_amount = match (original_amount, original_currency) { + (Some(original_amount), Some(original_currency)) => Some( + utils::to_currency_base_unit(original_amount, original_currency)?, + ), + _ => None, + }; + commerce_indicator = "recurring".to_string(); + ( + None, + None, + Some(WellsfargoAuthorizationOptions { + initiator: Some(WellsfargoPaymentInitiator { + initiator_type: Some(WellsfargoPaymentInitiatorTypes::Merchant), + credential_stored_on_file: None, + stored_credential_used: Some(true), + }), + merchant_intitiated_transaction: Some(MerchantInitiatedTransaction { + reason: Some("7".to_string()), + original_authorized_amount, + previous_transaction_id: Some(Secret::new(network_transaction_id)), + }), + }), + ) + } + None => (None, None, None), + } + } else { + (None, None, None) + }; + // this logic is for external authenticated card + let commerce_indicator_for_external_authentication = item + .router_data + .request + .authentication_data + .as_ref() + .and_then(|authn_data| { + authn_data + .eci + .clone() + .map(|eci| get_commerce_indicator_for_external_authentication(network, eci)) + }); + + 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: commerce_indicator_for_external_authentication + .unwrap_or(commerce_indicator), + }) + } } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct WellsfargoCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +fn get_commerce_indicator_for_external_authentication( + card_network: Option, + eci: String, +) -> String { + let card_network_lower_case = card_network + .as_ref() + .map(|card_network| card_network.to_lowercase()); + match eci.as_str() { + "00" | "01" | "02" => { + if matches!( + card_network_lower_case.as_deref(), + Some("mastercard") | Some("maestro") + ) { + "spa" + } else { + "internet" + } + } + "05" => match card_network_lower_case.as_deref() { + Some("amex") => "aesk", + Some("discover") => "dipb", + Some("mastercard") => "spa", + Some("visa") => "vbv", + Some("diners") => "pb", + Some("upi") => "up3ds", + _ => "internet", + }, + "06" => match card_network_lower_case.as_deref() { + Some("amex") => "aesk_attempted", + Some("discover") => "dipb_attempted", + Some("mastercard") => "spa", + Some("visa") => "vbv_attempted", + Some("diners") => "pb_attempted", + Some("upi") => "up3ds_attempted", + _ => "internet", + }, + "07" => match card_network_lower_case.as_deref() { + Some("amex") => "internet", + Some("discover") => "internet", + Some("mastercard") => "spa", + Some("visa") => "vbv_failure", + Some("diners") => "internet", + Some("upi") => "up3ds_failure", + _ => "internet", + }, + _ => "vbv_failure", + } + .to_string() +} + +impl + From<( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + Option, + )> for OrderInformationWithBill +{ + fn from( + (item, bill_to): ( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + Option, + ), + ) -> Self { + Self { + amount_details: Amount { + total_amount: item.amount.to_owned(), + currency: item.router_data.request.currency, + }, + bill_to, + } + } +} + +fn get_phone_number(item: Option<&payments::Address>) -> Option> { + item.as_ref() + .and_then(|billing| billing.phone.as_ref()) + .and_then(|phone| { + phone.number.as_ref().and_then(|number| { + phone + .country_code + .as_ref() + .map(|cc| Secret::new(format!("{}{}", cc, number.peek()))) + }) + }) +} + +fn build_bill_to( + address_details: Option<&payments::Address>, + email: pii::Email, +) -> Result> { + let phone_number = get_phone_number(address_details); + println!(" ADESSS {:?}", address_details.clone()); + let default_address = BillTo { + first_name: None, + last_name: None, + address1: None, + locality: None, + administrative_area: None, + postal_code: None, + country: None, + email: email.clone(), + phone_number: phone_number.clone(), + }; + let ad = Ok(address_details + .and_then(|addr| { + addr.address.as_ref().map(|addr| BillTo { + first_name: addr.first_name.clone(), + last_name: addr.last_name.clone(), + address1: addr.line1.clone(), + locality: addr.city.clone(), + administrative_area: addr.to_state_code_as_optional().ok().flatten(), + postal_code: addr.zip.clone(), + country: addr.country, + email, + phone_number: phone_number.clone(), + }) + }) + .unwrap_or(default_address)); + ad +} + +impl ForeignFrom for Vec { + fn foreign_from(metadata: Value) -> Self { + let hashmap: std::collections::BTreeMap = + serde_json::from_str(&metadata.to_string()) + .unwrap_or(std::collections::BTreeMap::new()); + let mut vector: Self = Self::new(); + let mut iter = 1; + for (key, value) in hashmap { + vector.push(MerchantDefinedInformation { + key: iter, + value: format!("{key}={value}"), + }); + iter += 1; + } + vector + } +} + +impl + TryFrom<( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + domain::Card, + )> for WellsfargoPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, ccard): ( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + domain::Card, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; + let order_information = OrderInformationWithBill::from((item, 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 payment_information = PaymentInformation::Cards(Box::new(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: Some(ccard.card_cvc), + card_type: card_type.clone(), + }, + })); + + let processing_information = ProcessingInformation::try_from((item, None, card_type))?; + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(Vec::::foreign_from); + + let consumer_authentication_information = item + .router_data + .request + .authentication_data + .as_ref() + .map(|authn_data| { + let (ucaf_authentication_data, cavv) = + if ccard.card_network == Some(common_enums::CardNetwork::Mastercard) { + (Some(Secret::new(authn_data.cavv.clone())), None) + } else { + (None, Some(authn_data.cavv.clone())) + }; + WellsfargoConsumerAuthInformation { + ucaf_collection_indicator: None, + cavv, + ucaf_authentication_data, + xid: Some(authn_data.threeds_server_transaction_id.clone()), + directory_server_transaction_id: authn_data + .ds_trans_id + .clone() + .map(Secret::new), + specification_version: None, + pa_specification_version: Some(authn_data.message_version.clone()), + veres_enrolled: Some("Y".to_string()), + } + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information, + merchant_defined_information, + }) + } +} + +impl + TryFrom<( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + domain::ApplePayWalletData, + )> for WellsfargoPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, apple_pay_data, apple_pay_wallet_data): ( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + domain::ApplePayWalletData, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; + 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(Box::new(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 merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(Vec::::foreign_from); + let ucaf_collection_indicator = match apple_pay_wallet_data + .payment_method + .network + .to_lowercase() + .as_str() + { + "mastercard" => Some("2".to_string()), + _ => None, + }; + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: Some(WellsfargoConsumerAuthInformation { + ucaf_collection_indicator, + cavv: None, + ucaf_authentication_data: None, + xid: None, + directory_server_transaction_id: None, + specification_version: None, + pa_specification_version: None, + veres_enrolled: None, + }), + merchant_defined_information, + }) + } +} + +impl + TryFrom<( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + domain::GooglePayWalletData, + )> for WellsfargoPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, google_pay_data): ( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + domain::GooglePayWalletData, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + + let payment_information = + PaymentInformation::GooglePay(Box::new(GooglePayPaymentInformation { + fluid_data: FluidData { + value: Secret::from( + consts::BASE64_ENGINE.encode(google_pay_data.tokenization_data.token), + ), + descriptor: None, + }, + })); + let processing_information = + 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(Vec::::foreign_from); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: None, + merchant_defined_information, + }) + } +} + +impl TryFrom> for AccountType { + type Error = error_stack::Report; + + fn try_from(optional_bank_type: Option) -> Result { + match optional_bank_type { + None => Err(errors::ConnectorError::MissingRequiredField { + field_name: "bank_type", + })?, + Some(bank_type) => match bank_type { + common_enums::BankType::Checking => Ok(Self::C), + common_enums::BankType::Savings => Ok(Self::S), + }, + } + } +} + +impl + TryFrom<( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + domain::BankDebitData, + )> for WellsfargoPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, bank_debit_data): ( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + domain::BankDebitData, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + let payment_information = match bank_debit_data { + domain::BankDebitData::AchBankDebit { + account_number, + routing_number, + bank_type, + .. + } => Ok(PaymentInformation::AchDebitPayment(Box::new( + AchPaymentInformation { + bank: AchBankAccount { + account: Account { + account_type: AccountType::try_from(bank_type)?, + number: account_number, + }, + routing_number, + }, + }, + ))), + domain::BankDebitData::SepaBankDebit { .. } + | domain::BankDebitData::BacsBankDebit { .. } + | domain::BankDebitData::BecsBankDebit { .. } => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wellsfargo"), + )) + } + }?; + let processing_information = + ProcessingInformation::try_from((item, Some(PaymentSolution::GooglePay), None))?; + let client_reference_information = ClientReferenceInformation::from(item); + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: None, + merchant_defined_information: None, + }) + } } impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> @@ -47,107 +1137,1038 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> fn try_from( item: &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(req_card) => { - let card = WellsfargoCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.clone(), - card, - }) + 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() { + domain::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + domain::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", + "Wellsfargo" + ))? + } + }, + None => { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to( + item.router_data.get_optional_billing(), + email, + )?; + let order_information = + OrderInformationWithBill::from((item, Some(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( + Box::new(ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + descriptor: Some(FLUID_DATA_DESCRIPTOR.to_string()), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }), + ); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from( + metadata, + ) + }); + 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, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: Some( + WellsfargoConsumerAuthInformation { + ucaf_collection_indicator, + cavv: None, + ucaf_authentication_data: None, + xid: None, + directory_server_transaction_id: None, + specification_version: None, + pa_specification_version: None, + veres_enrolled: None, + }, + ), + }) + } + } + } + domain::WalletData::GooglePay(google_pay_data) => { + Self::try_from((item, google_pay_data)) + } + domain::WalletData::AliPayQr(_) + | domain::WalletData::AliPayRedirect(_) + | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::MomoRedirect(_) + | domain::WalletData::KakaoPayRedirect(_) + | domain::WalletData::GoPayRedirect(_) + | domain::WalletData::GcashRedirect(_) + | domain::WalletData::ApplePayRedirect(_) + | domain::WalletData::ApplePayThirdPartySdk(_) + | domain::WalletData::DanaRedirect {} + | domain::WalletData::GooglePayRedirect(_) + | domain::WalletData::GooglePayThirdPartySdk(_) + | domain::WalletData::MbWayRedirect(_) + | domain::WalletData::MobilePayRedirect(_) + | domain::WalletData::PaypalRedirect(_) + | domain::WalletData::PaypalSdk(_) + | domain::WalletData::SamsungPay(_) + | domain::WalletData::TwintRedirect {} + | domain::WalletData::VippsRedirect {} + | domain::WalletData::TouchNGoRedirect(_) + | domain::WalletData::WeChatPayRedirect(_) + | domain::WalletData::WeChatPayQr(_) + | domain::WalletData::CashappQr(_) + | domain::WalletData::SwishQr(_) + | domain::WalletData::Mifinity(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wellsfargo"), + ) + .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. + domain::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)) + } + domain::PaymentMethodData::BankDebit(bank_debit) => { + Self::try_from((item, bank_debit)) + } + domain::PaymentMethodData::CardRedirect(_) + | domain::PaymentMethodData::PayLater(_) + | domain::PaymentMethodData::BankRedirect(_) + | domain::PaymentMethodData::BankTransfer(_) + | domain::PaymentMethodData::Crypto(_) + | domain::PaymentMethodData::Reward + | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::OpenBanking(_) + | domain::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wellsfargo"), + ) + .into()) + } + } } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } -//TODO: Fill the struct with respective fields -// Auth Struct +impl + TryFrom<( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + String, + )> for WellsfargoPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, connector_mandate_id): ( + &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + String, + ), + ) -> Result { + let processing_information = ProcessingInformation::try_from((item, None, None))?; + let payment_instrument = WellsfargoPaymentInstrument { + id: connector_mandate_id.into(), + }; + let bill_to = + item.router_data.request.get_email().ok().and_then(|email| { + build_bill_to(item.router_data.get_optional_billing(), email).ok() + }); + let order_information = OrderInformationWithBill::from((item, bill_to)); + let payment_information = + PaymentInformation::MandatePayment(Box::new(MandatePaymentInformation { + payment_instrument, + })); + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(Vec::::foreign_from); + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoPaymentsCaptureRequest { + processing_information: ProcessingInformation, + order_information: OrderInformationWithBill, + client_reference_information: ClientReferenceInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoPaymentsIncrementalAuthorizationRequest { + processing_information: ProcessingInformation, + order_information: OrderInformationIncrementalAuthorization, +} + +impl TryFrom<&WellsfargoRouterData<&types::PaymentsCaptureRouterData>> + for WellsfargoPaymentsCaptureRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &WellsfargoRouterData<&types::PaymentsCaptureRouterData>, + ) -> Result { + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(Vec::::foreign_from); + Ok(Self { + processing_information: ProcessingInformation { + capture_options: Some(CaptureOptions { + capture_sequence_number: 1, + total_capture_count: 1, + }), + action_list: None, + action_token_types: None, + authorization_options: None, + capture: None, + commerce_indicator: String::from("internet"), + payment_solution: None, + }, + order_information: OrderInformationWithBill { + amount_details: Amount { + total_amount: item.amount.clone(), + currency: item.router_data.request.currency, + }, + bill_to: None, + }, + client_reference_information: ClientReferenceInformation { + code: Some(item.router_data.connector_request_reference_id.clone()), + }, + merchant_defined_information, + }) + } +} + +impl TryFrom<&WellsfargoRouterData<&types::PaymentsIncrementalAuthorizationRouterData>> + for WellsfargoPaymentsIncrementalAuthorizationRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &WellsfargoRouterData<&types::PaymentsIncrementalAuthorizationRouterData>, + ) -> Result { + Ok(Self { + processing_information: ProcessingInformation { + action_list: None, + action_token_types: None, + authorization_options: Some(WellsfargoAuthorizationOptions { + initiator: Some(WellsfargoPaymentInitiator { + initiator_type: None, + credential_stored_on_file: None, + stored_credential_used: Some(true), + }), + merchant_intitiated_transaction: Some(MerchantInitiatedTransaction { + reason: Some("5".to_owned()), + previous_transaction_id: None, + original_authorized_amount: None, + }), + }), + commerce_indicator: String::from("internet"), + capture: None, + capture_options: None, + payment_solution: None, + }, + order_information: OrderInformationIncrementalAuthorization { + amount_details: AdditionalAmount { + additional_amount: item.amount.clone(), + currency: item.router_data.request.currency.to_string(), + }, + }, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoVoidRequest { + client_reference_information: ClientReferenceInformation, + reversal_information: ReversalInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, + // The connector documentation does not mention the merchantDefinedInformation field for Void requests. But this has been still added because it works! +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ReversalInformation { + amount_details: Amount, + reason: String, +} + +impl TryFrom<&WellsfargoRouterData<&types::PaymentsCancelRouterData>> for WellsfargoVoidRequest { + type Error = error_stack::Report; + fn try_from( + value: &WellsfargoRouterData<&types::PaymentsCancelRouterData>, + ) -> Result { + let merchant_defined_information = value + .router_data + .request + .metadata + .clone() + .map(Vec::::foreign_from); + Ok(Self { + client_reference_information: ClientReferenceInformation { + code: Some(value.router_data.connector_request_reference_id.clone()), + }, + reversal_information: ReversalInformation { + amount_details: Amount { + total_amount: value.amount.to_owned(), + currency: value.router_data.request.currency.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "Currency", + }, + )?, + }, + reason: value + .router_data + .request + .cancellation_reason + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Cancellation Reason", + })?, + }, + merchant_defined_information, + }) + } +} + pub struct WellsfargoAuthType { pub(super) api_key: Secret, + pub(super) merchant_account: Secret, + pub(super) api_secret: Secret, } impl TryFrom<&types::ConnectorAuthType> for WellsfargoAuthType { type Error = error_stack::Report; fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - match auth_type { - types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + if let types::ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } = auth_type + { + Ok(Self { api_key: api_key.to_owned(), - }), - _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + merchant_account: key1.to_owned(), + api_secret: api_secret.to_owned(), + }) + } else { + Err(errors::ConnectorError::FailedToObtainAuthType)? } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum WellsfargoPaymentStatus { + Authorized, Succeeded, Failed, - #[default] - Processing, + Voided, + Reversed, + Pending, + Declined, + Rejected, + Challenge, + AuthorizedPendingReview, + AuthorizedRiskDeclined, + Transmitted, + InvalidRequest, + ServerError, + PendingAuthentication, + PendingReview, + Accepted, + Cancelled, + StatusNotReceived, + //PartialAuthorized, not being consumed yet. } -impl From for enums::AttemptStatus { - fn from(item: WellsfargoPaymentStatus) -> Self { - match item { - WellsfargoPaymentStatus::Succeeded => Self::Charged, - WellsfargoPaymentStatus::Failed => Self::Failure, - WellsfargoPaymentStatus::Processing => Self::Authorizing, +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum WellsfargoIncrementalAuthorizationStatus { + Authorized, + Declined, + AuthorizedPendingReview, +} + +impl ForeignFrom<(WellsfargoPaymentStatus, bool)> for enums::AttemptStatus { + fn foreign_from((status, capture): (WellsfargoPaymentStatus, bool)) -> Self { + match status { + WellsfargoPaymentStatus::Authorized + | WellsfargoPaymentStatus::AuthorizedPendingReview => { + if capture { + // Because Wellsfargo will return Payment Status as Authorized even in AutoCapture Payment + Self::Charged + } else { + Self::Authorized + } + } + WellsfargoPaymentStatus::Pending => { + if capture { + Self::Charged + } else { + Self::Pending + } + } + WellsfargoPaymentStatus::Succeeded | WellsfargoPaymentStatus::Transmitted => { + Self::Charged + } + WellsfargoPaymentStatus::Voided + | WellsfargoPaymentStatus::Reversed + | WellsfargoPaymentStatus::Cancelled => Self::Voided, + WellsfargoPaymentStatus::Failed + | WellsfargoPaymentStatus::Declined + | WellsfargoPaymentStatus::AuthorizedRiskDeclined + | WellsfargoPaymentStatus::Rejected + | WellsfargoPaymentStatus::InvalidRequest + | WellsfargoPaymentStatus::ServerError => Self::Failure, + WellsfargoPaymentStatus::PendingAuthentication => Self::AuthenticationPending, + WellsfargoPaymentStatus::PendingReview + | WellsfargoPaymentStatus::StatusNotReceived + | WellsfargoPaymentStatus::Challenge + | WellsfargoPaymentStatus::Accepted => Self::Pending, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +impl From for common_enums::AuthorizationStatus { + fn from(item: WellsfargoIncrementalAuthorizationStatus) -> Self { + match item { + WellsfargoIncrementalAuthorizationStatus::Authorized + | WellsfargoIncrementalAuthorizationStatus::AuthorizedPendingReview => Self::Success, + WellsfargoIncrementalAuthorizationStatus::Declined => Self::Failure, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] pub struct WellsfargoPaymentsResponse { - status: WellsfargoPaymentStatus, id: String, + status: Option, + client_reference_information: Option, + processor_information: Option, + risk_information: Option, + token_information: Option, + error_information: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoErrorInformationResponse { + id: String, + error_information: WellsfargoErrorInformation, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoPaymentsIncrementalAuthorizationResponse { + status: WellsfargoIncrementalAuthorizationStatus, + error_information: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientReferenceInformation { + code: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientProcessorInformation { + network_transaction_id: Option, + avs: Option, + card_verification: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CardVerification { + result_code: Option, + result_code_raw: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Avs { + code: Option, + code_raw: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientRiskInformation { + rules: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ClientRiskInformationRules { + name: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoTokenInformation { + payment_instrument: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct WellsfargoErrorInformation { + reason: Option, + message: Option, + details: Option>, } impl - TryFrom< + ForeignFrom<( + &WellsfargoErrorInformationResponse, types::ResponseRouterData, - > for types::RouterData + Option, + )> for types::RouterData +{ + fn foreign_from( + (error_response, item, transaction_status): ( + &WellsfargoErrorInformationResponse, + types::ResponseRouterData< + F, + WellsfargoPaymentsResponse, + T, + types::PaymentsResponseData, + >, + Option, + ), + ) -> Self { + let detailed_error_info = + error_response + .error_information + .details + .to_owned() + .map(|details| { + details + .iter() + .map(|details| format!("{} : {}", details.field, details.reason)) + .collect::>() + .join(", ") + }); + + let reason = get_error_reason( + error_response.error_information.message.clone(), + detailed_error_info, + None, + ); + let response = Err(types::ErrorResponse { + code: error_response + .error_information + .reason + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_response + .error_information + .reason + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }); + match transaction_status { + Some(status) => Self { + response, + status, + ..item.data + }, + None => Self { + response, + ..item.data + }, + } + } +} + +fn get_error_response_if_failure( + (info_response, status, http_code): (&WellsfargoPaymentsResponse, enums::AttemptStatus, u16), +) -> Option { + if utils::is_payment_failure(status) { + Some(types::ErrorResponse::foreign_from(( + &info_response.error_information, + &info_response.risk_information, + Some(status), + http_code, + info_response.id.clone(), + ))) + } else { + None + } +} + +fn get_payment_response( + (info_response, status, http_code): (&WellsfargoPaymentsResponse, enums::AttemptStatus, u16), +) -> Result { + let error_response = get_error_response_if_failure((info_response, status, http_code)); + match error_response { + Some(error) => Err(error), + None => { + let incremental_authorization_allowed = + Some(status == enums::AttemptStatus::Authorized); + let mandate_reference = + info_response + .token_information + .clone() + .map(|token_info| types::MandateReference { + connector_mandate_id: token_info + .payment_instrument + .map(|payment_instrument| 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, + connector_metadata: None, + network_txn_id: info_response.processor_information.as_ref().and_then( + |processor_information| processor_information.network_transaction_id.clone(), + ), + connector_response_reference_id: Some( + info_response + .client_reference_information + .clone() + .and_then(|client_reference_information| client_reference_information.code) + .unwrap_or(info_response.id.clone()), + ), + incremental_authorization_allowed, + charge_id: None, + }) + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + api::Authorize, + WellsfargoPaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + api::Authorize, + WellsfargoPaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + let status = enums::AttemptStatus::foreign_from(( + item.response + .status + .clone() + .unwrap_or(WellsfargoPaymentStatus::StatusNotReceived), + item.data.request.is_auto_capture()?, + )); + let response = get_payment_response((&item.response, status, item.http_code)); + let connector_response = item + .response + .processor_information + .as_ref() + .map(types::AdditionalPaymentMethodConnectorResponse::from) + .map(types::ConnectorResponseData::with_additional_payment_method_data); + + Ok(Self { + status, + response, + connector_response, + ..item.data + }) + } +} + +impl From<&ClientProcessorInformation> for types::AdditionalPaymentMethodConnectorResponse { + fn from(processor_information: &ClientProcessorInformation) -> Self { + let payment_checks = Some( + serde_json::json!({"avs_response": processor_information.avs, "card_verification": processor_information.card_verification}), + ); + + Self::Card { + authentication_data: None, + payment_checks, + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + WellsfargoPaymentsResponse, + types::PaymentsCaptureData, + types::PaymentsResponseData, + >, + > for types::RouterData { type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, WellsfargoPaymentsResponse, - T, + types::PaymentsCaptureData, types::PaymentsResponseData, >, ) -> Result { + let status = enums::AttemptStatus::foreign_from(( + item.response + .status + .clone() + .unwrap_or(WellsfargoPaymentStatus::StatusNotReceived), + true, + )); + let response = get_payment_response((&item.response, status, item.http_code)); Ok(Self { - status: enums::AttemptStatus::from(item.response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), + status, + response, ..item.data }) } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] +impl + TryFrom< + types::ResponseRouterData< + F, + WellsfargoPaymentsResponse, + types::PaymentsCancelData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + WellsfargoPaymentsResponse, + types::PaymentsCancelData, + types::PaymentsResponseData, + >, + ) -> Result { + let status = enums::AttemptStatus::foreign_from(( + item.response + .status + .clone() + .unwrap_or(WellsfargoPaymentStatus::StatusNotReceived), + false, + )); + let response = get_payment_response((&item.response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } +} + +// zero dollar response +impl + TryFrom< + types::ResponseRouterData< + api::SetupMandate, + WellsfargoPaymentsResponse, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + > + for types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + api::SetupMandate, + WellsfargoPaymentsResponse, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + ) -> Result { + let mandate_reference = + item.response + .token_information + .clone() + .map(|token_info| types::MandateReference { + connector_mandate_id: token_info + .payment_instrument + .map(|payment_instrument| payment_instrument.id.expose()), + payment_method_id: None, + }); + let mut mandate_status = enums::AttemptStatus::foreign_from(( + item.response + .status + .clone() + .unwrap_or(WellsfargoPaymentStatus::StatusNotReceived), + 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((&item.response, mandate_status, item.http_code)); + + let connector_response = item + .response + .processor_information + .as_ref() + .map(types::AdditionalPaymentMethodConnectorResponse::from) + .map(types::ConnectorResponseData::with_additional_payment_method_data); + + Ok(Self { + status: mandate_status, + response: match error_response { + Some(error) => Err(error), + None => Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.id.clone(), + ), + redirection_data: None, + mandate_reference, + connector_metadata: None, + network_txn_id: item.response.processor_information.as_ref().and_then( + |processor_information| { + processor_information.network_transaction_id.clone() + }, + ), + connector_response_reference_id: Some( + item.response + .client_reference_information + .and_then(|client_reference_information| { + client_reference_information.code.clone() + }) + .unwrap_or(item.response.id), + ), + incremental_authorization_allowed: Some( + mandate_status == enums::AttemptStatus::Authorized, + ), + charge_id: None, + }), + }, + connector_response, + ..item.data + }) + } +} + +impl + ForeignTryFrom<( + types::ResponseRouterData< + F, + WellsfargoPaymentsIncrementalAuthorizationResponse, + T, + types::PaymentsResponseData, + >, + bool, + )> for types::RouterData +{ + type Error = error_stack::Report; + fn foreign_try_from( + data: ( + types::ResponseRouterData< + F, + WellsfargoPaymentsIncrementalAuthorizationResponse, + T, + types::PaymentsResponseData, + >, + bool, + ), + ) -> Result { + let item = data.0; + Ok(Self { + response: match item.response.error_information { + Some(error) => Ok( + types::PaymentsResponseData::IncrementalAuthorizationResponse { + status: common_enums::AuthorizationStatus::Failure, + error_code: error.reason, + error_message: error.message, + connector_authorization_id: None, + }, + ), + _ => Ok( + types::PaymentsResponseData::IncrementalAuthorizationResponse { + status: item.response.status.into(), + error_code: None, + error_message: None, + connector_authorization_id: None, + }, + ), + }, + ..item.data + }) + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoTransactionResponse { + id: String, + application_information: ApplicationInformation, + client_reference_information: Option, + error_information: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplicationInformation { + status: Option, +} + +impl + TryFrom< + types::ResponseRouterData< + F, + WellsfargoTransactionResponse, + types::PaymentsSyncData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + WellsfargoTransactionResponse, + types::PaymentsSyncData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response.application_information.status { + Some(status) => { + let status = enums::AttemptStatus::foreign_from(( + status, + item.data.request.is_auto_capture()?, + )); + let incremental_authorization_allowed = + Some(status == enums::AttemptStatus::Authorized); + let risk_info: Option = None; + if utils::is_payment_failure(status) { + Ok(Self { + response: Err(types::ErrorResponse::foreign_from(( + &item.response.error_information, + &risk_info, + Some(status), + item.http_code, + item.response.id.clone(), + ))), + status: enums::AttemptStatus::Failure, + ..item.data + }) + } else { + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.id.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: item + .response + .client_reference_information + .map(|cref| cref.code) + .unwrap_or(Some(item.response.id)), + incremental_authorization_allowed, + charge_id: None, + }), + ..item.data + }) + } + } + None => Ok(Self { + status: item.data.status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.id.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.id), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct WellsfargoRefundRequest { - pub amount: StringMinorUnit, + order_information: OrderInformation, + client_reference_information: ClientReferenceInformation, } impl TryFrom<&WellsfargoRouterData<&types::RefundsRouterData>> for WellsfargoRefundRequest { @@ -156,79 +2177,319 @@ impl TryFrom<&WellsfargoRouterData<&types::RefundsRouterData>> for Wellsfa item: &WellsfargoRouterData<&types::RefundsRouterData>, ) -> Result { Ok(Self { - amount: item.amount.to_owned(), + order_information: OrderInformation { + amount_details: Amount { + total_amount: item.amount.clone(), + currency: item.router_data.request.currency, + }, + }, + client_reference_information: ClientReferenceInformation { + code: Some(item.router_data.request.refund_id.clone()), + }, }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { +impl From for enums::RefundStatus { + fn from(item: WellsfargoRefundStatus) -> Self { match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping + WellsfargoRefundStatus::Succeeded | WellsfargoRefundStatus::Transmitted => { + Self::Success + } + WellsfargoRefundStatus::Cancelled + | WellsfargoRefundStatus::Failed + | WellsfargoRefundStatus::Voided => Self::Failure, + WellsfargoRefundStatus::Pending => Self::Pending, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum WellsfargoRefundStatus { + Succeeded, + Transmitted, + Failed, + Pending, + Voided, + Cancelled, } -impl TryFrom> +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoRefundResponse { + id: String, + status: WellsfargoRefundStatus, + error_information: Option, +} + +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: types::RefundsResponseRouterData, ) -> Result { - Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.id.to_string(), + let refund_status = enums::RefundStatus::from(item.response.status.clone()); + let response = if utils::is_refund_failure(refund_status) { + Err(types::ErrorResponse::foreign_from(( + &item.response.error_information, + &None, + None, + item.http_code, + item.response.id.clone(), + ))) + } else { + Ok(types::RefundsResponseData { + connector_refund_id: item.response.id, refund_status: enums::RefundStatus::from(item.response.status), - }), + }) + }; + + Ok(Self { + response, ..item.data }) } } -impl TryFrom> +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RsyncApplicationInformation { + status: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoRsyncResponse { + id: String, + application_information: Option, + error_information: Option, +} + +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: types::RefundsResponseRouterData, ) -> Result { - Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + let response = match item + .response + .application_information + .and_then(|application_information| application_information.status) + { + Some(status) => { + let refund_status = enums::RefundStatus::from(status.clone()); + if utils::is_refund_failure(refund_status) { + if status == WellsfargoRefundStatus::Voided { + Err(types::ErrorResponse::foreign_from(( + &Some(WellsfargoErrorInformation { + message: Some(consts::REFUND_VOIDED.to_string()), + reason: Some(consts::REFUND_VOIDED.to_string()), + details: None, + }), + &None, + None, + item.http_code, + item.response.id.clone(), + ))) + } else { + Err(types::ErrorResponse::foreign_from(( + &item.response.error_information, + &None, + None, + item.http_code, + item.response.id.clone(), + ))) + } + } else { + Ok(types::RefundsResponseData { + connector_refund_id: item.response.id, + refund_status, + }) + } + } + + None => Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.clone(), + refund_status: match item.data.response { + Ok(response) => response.refund_status, + Err(_) => common_enums::RefundStatus::Pending, + }, }), + }; + + Ok(Self { + response, ..item.data }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct WellsfargoErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoStandardErrorResponse { + pub error_information: Option, + pub status: Option, + pub message: Option, pub reason: Option, + pub details: Option>, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoNotAvailableErrorResponse { + pub errors: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoNotAvailableErrorObject { + #[serde(rename = "type")] + pub error_type: Option, + pub message: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WellsfargoServerErrorResponse { + pub status: Option, + pub message: Option, + pub reason: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum Reason { + SystemError, + ServerTimeout, + ServiceTimeout, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct WellsfargoAuthenticationErrorResponse { + pub response: AuthenticationErrorInformation, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum WellsfargoErrorResponse { + AuthenticationError(Box), + //If the request resource is not available/exists in wellsfargo + NotAvailableError(Box), + StandardError(Box), +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Details { + pub field: String, + pub reason: String, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct ErrorInformation { + pub message: String, + pub reason: String, + pub details: Option>, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct AuthenticationErrorInformation { + pub rmsg: String, +} + +impl + ForeignFrom<( + &Option, + &Option, + Option, + u16, + String, + )> for types::ErrorResponse +{ + fn foreign_from( + (error_data, risk_information, attempt_status, status_code, transaction_id): ( + &Option, + &Option, + Option, + u16, + String, + ), + ) -> Self { + let avs_message = risk_information + .clone() + .map(|client_risk_information| { + client_risk_information.rules.map(|rules| { + rules + .iter() + .map(|risk_info| { + risk_info.name.clone().map_or("".to_string(), |name| { + format!(" , {}", name.clone().expose()) + }) + }) + .collect::>() + .join("") + }) + }) + .unwrap_or(Some("".to_string())); + + let detailed_error_info = error_data + .clone() + .map(|error_data| match error_data.details { + Some(details) => details + .iter() + .map(|details| format!("{} : {}", details.field, details.reason)) + .collect::>() + .join(", "), + None => "".to_string(), + }); + + let reason = get_error_reason( + error_data.clone().and_then(|error_info| error_info.message), + detailed_error_info, + avs_message, + ); + let error_message = error_data.clone().and_then(|error_info| error_info.reason); + Self { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code, + attempt_status, + connector_transaction_id: Some(transaction_id.clone()), + } + } +} + +pub fn get_error_reason( + error_info: Option, + detailed_error_info: Option, + avs_error_info: Option, +) -> Option { + match (error_info, detailed_error_info, avs_error_info) { + (Some(message), Some(details), Some(avs_message)) => Some(format!( + "{}, detailed_error_information: {}, avs_message: {}", + message, details, avs_message + )), + (Some(message), Some(details), None) => Some(format!( + "{}, detailed_error_information: {}", + message, details + )), + (Some(message), None, Some(avs_message)) => { + Some(format!("{}, avs_message: {}", message, avs_message)) + } + (None, Some(details), Some(avs_message)) => { + Some(format!("{}, avs_message: {}", details, avs_message)) + } + (Some(message), None, None) => Some(message), + (None, Some(details), None) => Some(details), + (None, None, Some(avs_message)) => Some(avs_message), + (None, None, None) => None, + } } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index b8e9d7c4f1..dabdf4bd32 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1484,10 +1484,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { volt::transformers::VoltAuthType::try_from(self.auth_type)?; Ok(()) } - // api_enums::Connector::Wellsfargo => { - // wellsfargo::transformers::WellsfargoAuthType::try_from(self.auth_type)?; - // Ok(()) - // } + api_enums::Connector::Wellsfargo => { + wellsfargo::transformers::WellsfargoAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Wise => { wise::transformers::WiseAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index f5abd22480..72264c4eff 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -230,8 +230,8 @@ default_imp_for_complete_authorize!( connector::Trustpay, connector::Tsys, connector::Volt, - connector::Wellsfargo, connector::Wise, + connector::Wellsfargo, connector::Worldline, connector::Worldpay, connector::Zen, @@ -2792,7 +2792,6 @@ default_imp_for_incremental_authorization!( connector::Trustpay, connector::Tsys, connector::Volt, - connector::Wellsfargo, connector::Wise, connector::Worldline, connector::Worldpay, @@ -2882,7 +2881,6 @@ default_imp_for_revoking_mandates!( connector::Trustpay, connector::Tsys, connector::Volt, - connector::Wellsfargo, connector::Wise, connector::Worldline, connector::Worldpay, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 09782b5af5..ccc9639852 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -468,9 +468,9 @@ impl ConnectorData { } enums::Connector::Tsys => Ok(ConnectorEnum::Old(Box::new(&connector::Tsys))), enums::Connector::Volt => Ok(ConnectorEnum::Old(Box::new(connector::Volt::new()))), - // enums::Connector::Wellsfargo => { - // Ok(ConnectorEnum::Old(Box::new(connector::Wellsfargo::new()))) - // } + enums::Connector::Wellsfargo => { + Ok(ConnectorEnum::Old(Box::new(&connector::Wellsfargo))) + } enums::Connector::Zen => Ok(ConnectorEnum::Old(Box::new(&connector::Zen))), enums::Connector::Zsl => Ok(ConnectorEnum::Old(Box::new(&connector::Zsl))), enums::Connector::Plaid => { diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index d25d9e9c39..467867ef84 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -298,7 +298,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Trustpay => Self::Trustpay, api_enums::Connector::Tsys => Self::Tsys, api_enums::Connector::Volt => Self::Volt, - // api_enums::Connector::Wellsfargo => Self::Wellsfargo, + api_enums::Connector::Wellsfargo => Self::Wellsfargo, api_enums::Connector::Wise => Self::Wise, api_enums::Connector::Worldline => Self::Worldline, api_enums::Connector::Worldpay => Self::Worldpay, diff --git a/crates/router/tests/connectors/wellsfargo.rs b/crates/router/tests/connectors/wellsfargo.rs index 300a5fbb37..4425c07737 100644 --- a/crates/router/tests/connectors/wellsfargo.rs +++ b/crates/router/tests/connectors/wellsfargo.rs @@ -11,8 +11,8 @@ impl utils::Connector for WellsfargoTest { fn get_data(&self) -> api::ConnectorData { use router::connector::Wellsfargo; utils::construct_connector_data_old( - Box::new(Wellsfargo::new()), - types::Connector::Plaid, + Box::new(&Wellsfargo), + types::Connector::Wellsfargo, api::GetToken::Connector, None, ) diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js index 68c8fa61fc..c405a39913 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js @@ -15,6 +15,7 @@ function normalise(input) { bankofamerica: "Bank of America", cybersource: "Cybersource", paypal: "Paypal", + wellsfargo: "Wellsfargo" // Add more known exceptions here }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js index b22f5de535..b0a0455cc6 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js @@ -13,6 +13,7 @@ import { connectorDetails as nmiConnectorDetails } from "./Nmi.js"; import { connectorDetails as paypalConnectorDetails } from "./Paypal.js"; import { connectorDetails as stripeConnectorDetails } from "./Stripe.js"; import { connectorDetails as trustpayConnectorDetails } from "./Trustpay.js"; +import { connectorDetails as wellsfargoConnectorDetails } from "./WellsFargo.js"; const connectorDetails = { adyen: adyenConnectorDetails, @@ -27,6 +28,7 @@ const connectorDetails = { stripe: stripeConnectorDetails, trustpay: trustpayConnectorDetails, datatrans: datatransConnectorDetails, + wellsfargo: wellsfargoConnectorDetails }; export default function getConnectorDetails(connectorId) { diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js b/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js new file mode 100644 index 0000000000..d4bffec83c --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js @@ -0,0 +1,528 @@ +const successfulNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "25", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const successfulThreeDSTestCardDetails = { + card_number: "4000000000001091", + card_exp_month: "01", + card_exp_year: "25", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const singleUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_capture", + }, + }, + }, + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + trigger_skip: true, + + body: { + status: "requires_customer_action", + }, + }, + }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6500, + amount_capturable: 0, + amount_received: 6500, + }, + }, + }, + PartialCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6500, + amount_capturable: 0, + amount_received: 100, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + PartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + SyncRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + trigger_skip: true, + + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + trigger_skip: true, + + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + trigger_skip: true, + + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + trigger_skip: true, + + body: { + status: "requires_capture", + }, + }, + }, + ZeroAuthMandate: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PayoutUtils/Commons.js b/cypress-tests/cypress/e2e/PayoutUtils/Commons.js index 4036ee7f61..8eee62493b 100644 --- a/cypress-tests/cypress/e2e/PayoutUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PayoutUtils/Commons.js @@ -14,6 +14,7 @@ function normalise(input) { const exceptions = { adyenplatform: "Adyenplatform", wise: "Wise", + wellsfargo: "Wellsfargo" // Add more known exceptions here }; diff --git a/cypress-tests/cypress/support/redirectionHandler.js b/cypress-tests/cypress/support/redirectionHandler.js index f0998a44fc..8032c4274a 100644 --- a/cypress-tests/cypress/support/redirectionHandler.js +++ b/cypress-tests/cypress/support/redirectionHandler.js @@ -295,7 +295,7 @@ function threeDsRedirection(redirection_url, expected_url, connectorId) { cy.get('input[type="password"]').type("password"); cy.get("#buttonSubmit").click(); }); - } else if (connectorId === "bankofamerica" || connectorId === "cybersource") { + } else if (connectorId === "bankofamerica" || connectorId === "cybersource" || connectorId === "wellsfargo"){ cy.get("iframe", { timeout: TIMEOUT }) .its("0.contentDocument.body") .within((body) => {