diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 884e0fd82f..37d17dcff1 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -396,13 +396,11 @@ pub struct UnresolvedResponseReason { Clone, Debug, Eq, - PartialEq, serde::Deserialize, serde::Serialize, strum::Display, strum::EnumString, ToSchema, - Hash, )] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] @@ -430,6 +428,89 @@ pub enum FieldType { DropDown { options: Vec }, } +impl FieldType { + pub fn get_billing_variants() -> Vec { + vec![ + Self::UserBillingName, + Self::UserAddressLine1, + Self::UserAddressLine2, + Self::UserAddressCity, + Self::UserAddressPincode, + Self::UserAddressState, + Self::UserAddressCountry { options: vec![] }, + ] + } +} + +/// This implementatiobn is to ignore the inner value of UserAddressCountry enum while comparing +impl PartialEq for FieldType { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::UserCardNumber, Self::UserCardNumber) => true, + (Self::UserCardExpiryMonth, Self::UserCardExpiryMonth) => true, + (Self::UserCardExpiryYear, Self::UserCardExpiryYear) => true, + (Self::UserCardCvc, Self::UserCardCvc) => true, + (Self::UserFullName, Self::UserFullName) => true, + (Self::UserEmailAddress, Self::UserEmailAddress) => true, + (Self::UserPhoneNumber, Self::UserPhoneNumber) => true, + (Self::UserCountryCode, Self::UserCountryCode) => true, + ( + Self::UserCountry { + options: options_self, + }, + Self::UserCountry { + options: options_other, + }, + ) => options_self.eq(options_other), + ( + Self::UserCurrency { + options: options_self, + }, + Self::UserCurrency { + options: options_other, + }, + ) => options_self.eq(options_other), + (Self::UserBillingName, Self::UserBillingName) => true, + (Self::UserAddressLine1, Self::UserAddressLine1) => true, + (Self::UserAddressLine2, Self::UserAddressLine2) => true, + (Self::UserAddressCity, Self::UserAddressCity) => true, + (Self::UserAddressPincode, Self::UserAddressPincode) => true, + (Self::UserAddressState, Self::UserAddressState) => true, + (Self::UserAddressCountry { .. }, Self::UserAddressCountry { .. }) => true, + (Self::UserBlikCode, Self::UserBlikCode) => true, + (Self::UserBank, Self::UserBank) => true, + (Self::Text, Self::Text) => true, + ( + Self::DropDown { + options: options_self, + }, + Self::DropDown { + options: options_other, + }, + ) => options_self.eq(options_other), + _unused => false, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_partialeq_for_field_type() { + let user_address_country_is_us = FieldType::UserAddressCountry { + options: vec!["US".to_string()], + }; + + let user_address_country_is_all = FieldType::UserAddressCountry { + options: vec!["ALL".to_string()], + }; + + assert!(user_address_country_is_us.eq(&user_address_country_is_all)) + } +} + #[derive( Debug, serde::Deserialize, diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 96136d6937..b7a58b3b5b 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -537,7 +537,7 @@ impl From> for SurchargePercen } } /// Required fields info used while listing the payment_method_data -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq, ToSchema, Hash)] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq, ToSchema)] pub struct RequiredFieldInfo { /// Required field for a payment_method through a payment_method_type pub required_field: String, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 6009372489..20b94811ab 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -3869,6 +3869,24 @@ pub struct GpayAllowedMethodsParameters { pub allowed_auth_methods: Vec, /// The list of allowed card networks (ex: AMEX,JCB etc) pub allowed_card_networks: Vec, + /// Is billing address required + pub billing_address_required: Option, + /// Billing address parameters + pub billing_address_parameters: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct GpayBillingAddressParameters { + /// Is billing phone number required + pub phone_number_required: bool, + /// Billing address format + pub format: GpayBillingAddressFormat, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub enum GpayBillingAddressFormat { + FULL, + MIN, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] @@ -4215,6 +4233,8 @@ pub struct ApplePayPaymentRequest { /// The list of supported networks pub supported_networks: Option>, pub merchant_identifier: Option, + /// The required billing contact fields for connector + pub required_billing_contact_fields: Option>, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)] diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs index f89c2e5921..01332c9d29 100644 --- a/crates/connector_configs/src/transformer.rs +++ b/crates/connector_configs/src/transformer.rs @@ -291,6 +291,8 @@ impl DashboardRequestPayload { "MASTERCARD".to_string(), "VISA".to_string(), ], + billing_address_required: None, + billing_address_parameters: None, }; let allowed_payment_methods = payments::GpayAllowedPaymentMethods { payment_method_type: String::from("CARD"), diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 6c1a32fc22..12a507ffed 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -388,6 +388,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::GooglePayRedirectData, api_models::payments::GooglePayThirdPartySdk, api_models::payments::GooglePaySessionResponse, + api_models::payments::GpayBillingAddressParameters, + api_models::payments::GpayBillingAddressFormat, api_models::payments::SepaBankTransferInstructions, api_models::payments::BacsBankTransferInstructions, api_models::payments::RedirectResponse, diff --git a/crates/router/src/connector/bluesnap/transformers.rs b/crates/router/src/connector/bluesnap/transformers.rs index 0630fef274..251a928c09 100644 --- a/crates/router/src/connector/bluesnap/transformers.rs +++ b/crates/router/src/connector/bluesnap/transformers.rs @@ -538,6 +538,7 @@ impl TryFrom merchant_capabilities: None, supported_networks: None, merchant_identifier: None, + required_billing_contact_fields: None, }, ), connector: "payme".to_string(), diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 5a318e5e44..7f84e30191 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -1223,6 +1223,7 @@ pub fn get_apple_pay_session( ), total: apple_pay_init_result.total.into(), merchant_identifier: None, + required_billing_contact_fields: None, }), connector: "trustpay".to_string(), delayed_session_token: true, @@ -1326,6 +1327,8 @@ impl From for api_models::payments::GpayAllowedMet Self { allowed_auth_methods: value.allowed_auth_methods, allowed_card_networks: value.allowed_card_networks, + billing_address_required: None, + billing_address_parameters: None, } } } diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 66e0f14480..c83c73f762 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -11,9 +11,13 @@ use crate::{ payments::{self, access_token, helpers, transformers, PaymentData}, }, headers, logger, - routes::{self, metrics}, + routes::{self, app::settings, metrics}, services, - types::{self, api, domain}, + types::{ + self, + api::{self, enums}, + domain, + }, utils::OptionExt, }; @@ -78,6 +82,39 @@ impl Feature for types::PaymentsSessio } } +/// This function checks if for a given connector, payment_method and payment_method_type, +/// the list of required_field_type is present in dynamic fields +fn is_dynamic_fields_required( + required_fields: &settings::RequiredFields, + payment_method: enums::PaymentMethod, + payment_method_type: enums::PaymentMethodType, + connector: &types::Connector, + required_field_type: Vec, +) -> bool { + required_fields + .0 + .get(&payment_method) + .and_then(|pm_type| pm_type.0.get(&payment_method_type)) + .and_then(|required_fields_for_connector| { + required_fields_for_connector.fields.get(connector) + }) + .map(|required_fields_final| { + required_fields_final + .non_mandate + .iter() + .any(|(_, val)| required_field_type.contains(&val.field_type)) + || required_fields_final + .mandate + .iter() + .any(|(_, val)| required_field_type.contains(&val.field_type)) + || required_fields_final + .common + .iter() + .any(|(_, val)| required_field_type.contains(&val.field_type)) + }) + .unwrap_or(false) +} + fn get_applepay_metadata( connector_metadata: Option, ) -> RouterResult { @@ -247,6 +284,20 @@ async fn create_applepay_session_token( router_data.request.to_owned(), )?; + let billing_variants = enums::FieldType::get_billing_variants(); + + let required_billing_contact_fields = if is_dynamic_fields_required( + &state.conf.required_fields, + enums::PaymentMethod::Wallet, + enums::PaymentMethodType::ApplePay, + &connector.connector_name, + billing_variants, + ) { + Some(vec!["postalAddress".to_string()]) + } else { + None + }; + // Get apple pay payment request let applepay_payment_request = get_apple_pay_payment_request( amount_info, @@ -254,6 +305,7 @@ async fn create_applepay_session_token( router_data.request.to_owned(), apple_pay_session_request.merchant_identifier.as_str(), merchant_business_country, + required_billing_contact_fields, )?; let applepay_session_request = build_apple_pay_session_request( @@ -354,6 +406,7 @@ fn get_apple_pay_payment_request( session_data: types::PaymentsSessionData, merchant_identifier: &str, merchant_business_country: Option, + required_billing_contact_fields: Option>, ) -> RouterResult { let applepay_payment_request = payment_types::ApplePayPaymentRequest { country_code: merchant_business_country.or(session_data.country).ok_or( @@ -366,6 +419,7 @@ fn get_apple_pay_payment_request( merchant_capabilities: Some(payment_request_data.merchant_capabilities), supported_networks: Some(payment_request_data.supported_networks), merchant_identifier: Some(merchant_identifier.to_string()), + required_billing_contact_fields, }; Ok(applepay_payment_request) } @@ -443,6 +497,38 @@ fn create_gpay_session_token( expected_format: "gpay_metadata_format".to_string(), })?; + let billing_variants = enums::FieldType::get_billing_variants(); + + let is_billing_details_required = is_dynamic_fields_required( + &state.conf.required_fields, + enums::PaymentMethod::Wallet, + enums::PaymentMethodType::GooglePay, + &connector.connector_name, + billing_variants, + ); + + let billing_address_parameters = + is_billing_details_required.then_some(payment_types::GpayBillingAddressParameters { + phone_number_required: is_billing_details_required, + format: payment_types::GpayBillingAddressFormat::FULL, + }); + + let gpay_allowed_payment_methods = gpay_data + .data + .allowed_payment_methods + .into_iter() + .map( + |allowed_payment_methods| payment_types::GpayAllowedPaymentMethods { + parameters: payment_types::GpayAllowedMethodsParameters { + billing_address_required: Some(is_billing_details_required), + billing_address_parameters: billing_address_parameters.clone(), + ..allowed_payment_methods.parameters + }, + ..allowed_payment_methods + }, + ) + .collect(); + let session_data = router_data.request.clone(); let transaction_info = payment_types::GpayTransactionInfo { country_code: session_data.country.unwrap_or_default(), @@ -466,7 +552,7 @@ fn create_gpay_session_token( payment_types::GpaySessionTokenResponse::GooglePaySession( payment_types::GooglePaySessionResponse { merchant_info: gpay_data.data.merchant_info, - allowed_payment_methods: gpay_data.data.allowed_payment_methods, + allowed_payment_methods: gpay_allowed_payment_methods, transaction_info, connector: connector.connector_name.to_string(), sdk_next_action: payment_types::SdkNextAction { diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index cb82a2e767..d9061baebb 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -4999,6 +4999,14 @@ "merchant_identifier": { "type": "string", "nullable": true + }, + "required_billing_contact_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The required billing contact fields for connector", + "nullable": true } } }, @@ -9437,6 +9445,19 @@ "type": "string" }, "description": "The list of allowed card networks (ex: AMEX,JCB etc)" + }, + "billing_address_required": { + "type": "boolean", + "description": "Is billing address required", + "nullable": true + }, + "billing_address_parameters": { + "allOf": [ + { + "$ref": "#/components/schemas/GpayBillingAddressParameters" + } + ], + "nullable": true } } }, @@ -9460,6 +9481,29 @@ } } }, + "GpayBillingAddressFormat": { + "type": "string", + "enum": [ + "FULL", + "MIN" + ] + }, + "GpayBillingAddressParameters": { + "type": "object", + "required": [ + "phone_number_required", + "format" + ], + "properties": { + "phone_number_required": { + "type": "boolean", + "description": "Is billing phone number required" + }, + "format": { + "$ref": "#/components/schemas/GpayBillingAddressFormat" + } + } + }, "GpayMerchantInfo": { "type": "object", "required": [