From 650f3fa25c4130a2148862863ff444d16b41d2f3 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Tue, 14 May 2024 20:25:52 +0530 Subject: [PATCH] feat(payment_methods): pass required shipping details field for wallets session call based on `business_profile` config (#4616) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/admin.rs | 6 + crates/api_models/src/enums.rs | 28 ++ crates/api_models/src/payments.rs | 30 +- crates/diesel_models/src/business_profile.rs | 10 + crates/diesel_models/src/schema.rs | 1 + crates/openapi/src/openapi.rs | 4 + crates/router/src/configs/defaults.rs | 340 +++++++++++++++++- .../src/connector/bluesnap/transformers.rs | 1 + .../src/connector/payme/transformers.rs | 1 + .../src/connector/trustpay/transformers.rs | 7 + crates/router/src/core/admin.rs | 3 + .../router/src/core/payment_methods/cards.rs | 26 ++ crates/router/src/core/payments.rs | 15 +- crates/router/src/core/payments/flows.rs | 3 +- .../src/core/payments/flows/approve_flow.rs | 3 +- .../src/core/payments/flows/authorize_flow.rs | 3 +- .../src/core/payments/flows/cancel_flow.rs | 3 +- .../src/core/payments/flows/capture_flow.rs | 3 +- .../payments/flows/complete_authorize_flow.rs | 3 +- .../flows/incremental_authorization_flow.rs | 3 +- .../src/core/payments/flows/psync_flow.rs | 3 +- .../src/core/payments/flows/reject_flow.rs | 3 +- .../src/core/payments/flows/session_flow.rs | 79 +++- .../core/payments/flows/setup_mandate_flow.rs | 3 +- crates/router/src/core/payments/retry.rs | 5 + crates/router/src/core/routing/helpers.rs | 1 + crates/router/src/types/api/admin.rs | 2 + .../down.sql | 3 + .../up.sql | 3 + openapi/openapi_spec.json | 127 ++++++- 30 files changed, 692 insertions(+), 30 deletions(-) create mode 100644 migrations/2024-05-09-130152_collect_shipping_details_from_wallet_connector/down.sql create mode 100644 migrations/2024-05-09-130152_collect_shipping_details_from_wallet_connector/up.sql diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 25de882126..93cf7574d9 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -916,6 +916,9 @@ pub struct BusinessProfileCreate { /// Whether to use the billing details passed when creating the intent as payment method billing pub use_billing_as_payment_method_billing: Option, + + /// A boolean value to indicate if cusomter shipping details needs to be sent for wallets payments + pub collect_shipping_details_from_wallet_connector: Option, } #[derive(Clone, Debug, ToSchema, Serialize)] @@ -1055,6 +1058,9 @@ pub struct BusinessProfileUpdate { // Whether to use the billing details passed when creating the intent as payment method billing pub use_billing_as_payment_method_billing: Option, + + /// A boolean value to indicate if cusomter shipping details needs to be sent for wallets payments + pub collect_shipping_details_from_wallet_connector: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 37d17dcff1..866db8cc18 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -422,6 +422,13 @@ pub enum FieldType { UserAddressPincode, UserAddressState, UserAddressCountry { options: Vec }, + UserShippingName, + UserShippingAddressLine1, + UserShippingAddressLine2, + UserShippingAddressCity, + UserShippingAddressPincode, + UserShippingAddressState, + UserShippingAddressCountry { options: Vec }, UserBlikCode, UserBank, Text, @@ -440,6 +447,18 @@ impl FieldType { Self::UserAddressCountry { options: vec![] }, ] } + + pub fn get_shipping_variants() -> Vec { + vec![ + Self::UserShippingName, + Self::UserShippingAddressLine1, + Self::UserShippingAddressLine2, + Self::UserShippingAddressCity, + Self::UserShippingAddressPincode, + Self::UserShippingAddressState, + Self::UserShippingAddressCountry { options: vec![] }, + ] + } } /// This implementatiobn is to ignore the inner value of UserAddressCountry enum while comparing @@ -477,6 +496,15 @@ impl PartialEq for FieldType { (Self::UserAddressPincode, Self::UserAddressPincode) => true, (Self::UserAddressState, Self::UserAddressState) => true, (Self::UserAddressCountry { .. }, Self::UserAddressCountry { .. }) => true, + (Self::UserShippingName, Self::UserShippingName) => true, + (Self::UserShippingAddressLine1, Self::UserShippingAddressLine1) => true, + (Self::UserShippingAddressLine2, Self::UserShippingAddressLine2) => true, + (Self::UserShippingAddressCity, Self::UserShippingAddressCity) => true, + (Self::UserShippingAddressPincode, Self::UserShippingAddressPincode) => true, + (Self::UserShippingAddressState, Self::UserShippingAddressState) => true, + (Self::UserShippingAddressCountry { .. }, Self::UserShippingAddressCountry { .. }) => { + true + } (Self::UserBlikCode, Self::UserBlikCode) => true, (Self::UserBank, Self::UserBank) => true, (Self::Text, Self::Text) => true, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 20b94811ab..343763321d 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4098,6 +4098,12 @@ pub struct GooglePayThirdPartySdk { pub struct GooglePaySessionResponse { /// The merchant info pub merchant_info: GpayMerchantInfo, + /// Is shipping address required + pub shipping_address_required: bool, + /// Is email required + pub email_required: bool, + /// Shipping address parameters + pub shipping_address_parameters: GpayShippingAddressParameters, /// List of the allowed payment meythods pub allowed_payment_methods: Vec, /// The transaction info Google Pay requires @@ -4112,6 +4118,13 @@ pub struct GooglePaySessionResponse { pub secrets: Option, } +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub struct GpayShippingAddressParameters { + /// Is shipping phone number required + pub phone_number_required: bool, +} + #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] #[serde(rename_all = "lowercase")] pub struct KlarnaSessionTokenResponse { @@ -4234,7 +4247,22 @@ pub struct ApplePayPaymentRequest { pub supported_networks: Option>, pub merchant_identifier: Option, /// The required billing contact fields for connector - pub required_billing_contact_fields: Option>, + pub required_billing_contact_fields: Option, + /// The required shipping contacht fields for connector + pub required_shipping_contact_fields: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)] +pub struct ApplePayBillingContactFields(pub Vec); +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)] +pub struct ApplePayShippingContactFields(pub Vec); + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ApplePayAddressParameters { + PostalAddress, + Phone, + Email, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)] diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 41a938d83e..fb1b62e543 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -39,6 +39,7 @@ pub struct BusinessProfile { pub extended_card_info_config: Option, pub is_connector_agnostic_mit_enabled: Option, pub use_billing_as_payment_method_billing: Option, + pub collect_shipping_details_from_wallet_connector: Option, } #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)] @@ -69,6 +70,7 @@ pub struct BusinessProfileNew { pub extended_card_info_config: Option, pub is_connector_agnostic_mit_enabled: Option, pub use_billing_as_payment_method_billing: Option, + pub collect_shipping_details_from_wallet_connector: Option, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] @@ -96,6 +98,7 @@ pub struct BusinessProfileUpdateInternal { pub extended_card_info_config: Option, pub is_connector_agnostic_mit_enabled: Option, pub use_billing_as_payment_method_billing: Option, + pub collect_shipping_details_from_wallet_connector: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -120,6 +123,7 @@ pub enum BusinessProfileUpdate { authentication_connector_details: Option, extended_card_info_config: Option, use_billing_as_payment_method_billing: Option, + collect_shipping_details_from_wallet_connector: Option, }, ExtendedCardInfoUpdate { is_extended_card_info_enabled: Option, @@ -152,6 +156,7 @@ impl From for BusinessProfileUpdateInternal { authentication_connector_details, extended_card_info_config, use_billing_as_payment_method_billing, + collect_shipping_details_from_wallet_connector, } => Self { profile_name, modified_at, @@ -172,6 +177,7 @@ impl From for BusinessProfileUpdateInternal { authentication_connector_details, extended_card_info_config, use_billing_as_payment_method_billing, + collect_shipping_details_from_wallet_connector, ..Default::default() }, BusinessProfileUpdate::ExtendedCardInfoUpdate { @@ -217,6 +223,8 @@ impl From for BusinessProfile { is_extended_card_info_enabled: new.is_extended_card_info_enabled, extended_card_info_config: new.extended_card_info_config, use_billing_as_payment_method_billing: new.use_billing_as_payment_method_billing, + collect_shipping_details_from_wallet_connector: new + .collect_shipping_details_from_wallet_connector, } } } @@ -245,6 +253,7 @@ impl BusinessProfileUpdate { extended_card_info_config, is_connector_agnostic_mit_enabled, use_billing_as_payment_method_billing, + collect_shipping_details_from_wallet_connector, } = self.into(); BusinessProfile { profile_name: profile_name.unwrap_or(source.profile_name), @@ -270,6 +279,7 @@ impl BusinessProfileUpdate { is_connector_agnostic_mit_enabled, extended_card_info_config, use_billing_as_payment_method_billing, + collect_shipping_details_from_wallet_connector, ..source } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index a81c240e8b..e63c14eef1 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -195,6 +195,7 @@ diesel::table! { extended_card_info_config -> Nullable, is_connector_agnostic_mit_enabled -> Nullable, use_billing_as_payment_method_billing -> Nullable, + collect_shipping_details_from_wallet_connector -> Nullable, } } diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 12a507ffed..5f5597387b 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -344,6 +344,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::NoThirdPartySdkSessionResponse, api_models::payments::SecretInfoToInitiateSdk, api_models::payments::ApplePayPaymentRequest, + api_models::payments::ApplePayBillingContactFields, + api_models::payments::ApplePayShippingContactFields, + api_models::payments::ApplePayAddressParameters, api_models::payments::AmountInfo, api_models::payments::ProductType, api_models::payments::GooglePayWalletData, @@ -388,6 +391,7 @@ 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::GpayShippingAddressParameters, api_models::payments::GpayBillingAddressParameters, api_models::payments::GpayBillingAddressFormat, api_models::payments::SepaBankTransferInstructions, diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 4fcdd76992..667bc90fe4 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -6995,6 +6995,73 @@ impl Default for super::settings::RequiredFields { 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(), @@ -7082,6 +7149,73 @@ impl Default for super::settings::RequiredFields { 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(), @@ -7184,6 +7318,73 @@ impl Default for super::settings::RequiredFields { 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(), @@ -7411,6 +7612,73 @@ impl Default for super::settings::RequiredFields { 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(), @@ -7436,7 +7704,77 @@ impl Default for super::settings::RequiredFields { enums::Connector::Stripe, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "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/bluesnap/transformers.rs b/crates/router/src/connector/bluesnap/transformers.rs index 251a928c09..d1dbb9d92e 100644 --- a/crates/router/src/connector/bluesnap/transformers.rs +++ b/crates/router/src/connector/bluesnap/transformers.rs @@ -539,6 +539,7 @@ impl TryFrom supported_networks: None, merchant_identifier: None, required_billing_contact_fields: None, + required_shipping_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 7f84e30191..db225585f4 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -1224,6 +1224,7 @@ pub fn get_apple_pay_session( total: apple_pay_init_result.total.into(), merchant_identifier: None, required_billing_contact_fields: None, + required_shipping_contact_fields: None, }), connector: "trustpay".to_string(), delayed_session_token: true, @@ -1281,6 +1282,12 @@ pub fn get_google_pay_session( .collect(), transaction_info: google_pay_init_result.transaction_info.into(), secrets: Some((*secrets).clone().into()), + shipping_address_required: false, + email_required: false, + shipping_address_parameters: + api_models::payments::GpayShippingAddressParameters { + phone_number_required: false, + }, }, ), ))), diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 422b88ee9e..288de66de0 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -441,6 +441,7 @@ pub async fn update_business_profile_cascade( authentication_connector_details: None, extended_card_info_config: None, use_billing_as_payment_method_billing: None, + collect_shipping_details_from_wallet_connector: None, }; let update_futures = business_profiles.iter().map(|business_profile| async { @@ -1693,6 +1694,8 @@ pub async fn update_business_profile( })?, extended_card_info_config, use_billing_as_payment_method_billing: request.use_billing_as_payment_method_billing, + collect_shipping_details_from_wallet_connector: request + .collect_shipping_details_from_wallet_connector, }; let updated_business_profile = db diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 307f2dcd08..d3870a9e59 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -2197,6 +2197,7 @@ pub async fn list_payment_methods( let mut bank_transfer_consolidated_hm = HashMap::>::new(); + // All the required fields will be stored here and later filtered out based on business profile config let mut required_fields_hm = HashMap::< api_enums::PaymentMethod, HashMap>, @@ -2235,6 +2236,31 @@ pub async fn list_payment_methods( } } + let should_send_shipping_details = + business_profile.clone().and_then(|business_profile| { + business_profile + .collect_shipping_details_from_wallet_connector + }); + + // Remove shipping fields from required fields based on business profile configuration + if should_send_shipping_details != Some(true) { + let shipping_variants = + api_enums::FieldType::get_shipping_variants(); + + let keys_to_be_removed = required_fields_hs + .iter() + .filter(|(_key, value)| { + shipping_variants.contains(&value.field_type) + }) + .map(|(key, _value)| key.to_string()) + .collect::>(); + + keys_to_be_removed.iter().for_each(|key_to_be_removed| { + required_fields_hs.remove(key_to_be_removed); + }); + } + + // get the config, check the enums while adding { for (key, val) in &mut required_fields_hs { let temp = req_val diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 46eea17ba2..1d25db6a0b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -296,6 +296,7 @@ where frm_info.as_ref().and_then(|fi| fi.suggested_action), #[cfg(not(feature = "frm"))] None, + &business_profile, ) .await?; @@ -365,6 +366,7 @@ where frm_info.as_ref().and_then(|fi| fi.suggested_action), #[cfg(not(feature = "frm"))] None, + &business_profile, ) .await?; @@ -397,6 +399,7 @@ where frm_info.as_ref().and_then(|fi| fi.suggested_action), #[cfg(not(feature = "frm"))] None, + &business_profile, ) .await?; }; @@ -450,6 +453,7 @@ where payment_data, &customer, session_surcharge_details, + &business_profile, )) .await? } @@ -1381,6 +1385,7 @@ pub async fn call_connector_service( schedule_time: Option, header_payload: HeaderPayload, frm_suggestion: Option, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult> where F: Send + Clone + Sync, @@ -1577,7 +1582,13 @@ where // and rely on previous status set in router_data router_data.status = payment_data.payment_attempt.status; router_data - .decide_flows(state, &connector, call_connector_action, connector_request) + .decide_flows( + state, + &connector, + call_connector_action, + connector_request, + business_profile, + ) .await } else { Ok(router_data) @@ -1638,6 +1649,7 @@ pub async fn call_multiple_connectors_service( mut payment_data: PaymentData, customer: &Option, session_surcharge_details: Option, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult> where Op: Debug, @@ -1700,6 +1712,7 @@ where &session_connector_data.connector, CallConnectorAction::Trigger, None, + business_profile, ); join_handlers.push(res); diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index e1bd785830..a8425896dd 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -21,7 +21,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -46,6 +46,7 @@ pub trait Feature { connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, connector_request: Option, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult where Self: Sized, diff --git a/crates/router/src/core/payments/flows/approve_flow.rs b/crates/router/src/core/payments/flows/approve_flow.rs index ffa11fdb0f..92f815f6b5 100644 --- a/crates/router/src/core/payments/flows/approve_flow.rs +++ b/crates/router/src/core/payments/flows/approve_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -51,6 +51,7 @@ impl Feature _connector: &api::ConnectorData, _call_connector_action: payments::CallConnectorAction, _connector_request: Option, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { Err(ApiErrorResponse::NotImplemented { message: NotImplementedMessage::Reason("Flow not supported".to_string()), diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index b2773ea9aa..a770454b20 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -14,7 +14,7 @@ use crate::{ logger, routes::{metrics, AppState}, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -63,6 +63,7 @@ impl Feature for types::PaymentsAu connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, connector_request: Option, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { let connector_integration: services::BoxedConnectorIntegration< '_, diff --git a/crates/router/src/core/payments/flows/cancel_flow.rs b/crates/router/src/core/payments/flows/cancel_flow.rs index 5f802a0bbe..0e90ab40b6 100644 --- a/crates/router/src/core/payments/flows/cancel_flow.rs +++ b/crates/router/src/core/payments/flows/cancel_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::{metrics, AppState}, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -50,6 +50,7 @@ impl Feature connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, connector_request: Option, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { metrics::PAYMENT_CANCEL_COUNT.add( &metrics::CONTEXT, diff --git a/crates/router/src/core/payments/flows/capture_flow.rs b/crates/router/src/core/payments/flows/capture_flow.rs index 56ae6500c3..b979eb2a33 100644 --- a/crates/router/src/core/payments/flows/capture_flow.rs +++ b/crates/router/src/core/payments/flows/capture_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -51,6 +51,7 @@ impl Feature connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, connector_request: Option, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { let connector_integration: services::BoxedConnectorIntegration< '_, diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index 667fa3feed..4e9bf47232 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::{metrics, AppState}, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, utils::OptionExt, }; @@ -65,6 +65,7 @@ impl Feature connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, connector_request: Option, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { let connector_integration: services::BoxedConnectorIntegration< '_, diff --git a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs index e702483022..716228d73f 100644 --- a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs +++ b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -58,6 +58,7 @@ impl Feature, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { let connector_integration: services::BoxedConnectorIntegration< '_, diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index f410b65f07..d1e2e4b0ab 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -10,7 +10,7 @@ use crate::{ }, routes::AppState, services::{self, logger}, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -54,6 +54,7 @@ impl Feature connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, connector_request: Option, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { let connector_integration: services::BoxedConnectorIntegration< '_, diff --git a/crates/router/src/core/payments/flows/reject_flow.rs b/crates/router/src/core/payments/flows/reject_flow.rs index 89c1585fca..726993aa7f 100644 --- a/crates/router/src/core/payments/flows/reject_flow.rs +++ b/crates/router/src/core/payments/flows/reject_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -50,6 +50,7 @@ impl Feature _connector: &api::ConnectorData, _call_connector_action: payments::CallConnectorAction, _connector_request: Option, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { Err(ApiErrorResponse::NotImplemented { message: NotImplementedMessage::Reason("Flow not supported".to_string()), diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index c83c73f762..e0837be251 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -16,7 +16,7 @@ use crate::{ types::{ self, api::{self, enums}, - domain, + domain, storage, }, utils::OptionExt, }; @@ -59,6 +59,7 @@ impl Feature for types::PaymentsSessio connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, _connector_request: Option, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { metrics::SESSION_TOKEN_CREATED.add( &metrics::CONTEXT, @@ -68,8 +69,14 @@ impl Feature for types::PaymentsSessio connector.connector_name.to_string(), )], ); - self.decide_flow(state, connector, Some(true), call_connector_action) - .await + self.decide_flow( + state, + connector, + Some(true), + call_connector_action, + business_profile, + ) + .await } async fn add_access_token<'a>( @@ -173,6 +180,7 @@ async fn create_applepay_session_token( state: &routes::AppState, router_data: &types::PaymentsSessionRouterData, connector: &api::ConnectorData, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { let delayed_response = is_session_response_delayed(state, connector); if delayed_response { @@ -286,17 +294,36 @@ async fn create_applepay_session_token( let billing_variants = enums::FieldType::get_billing_variants(); - let required_billing_contact_fields = if is_dynamic_fields_required( + let required_billing_contact_fields = 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 - }; + ) + .then_some(payment_types::ApplePayBillingContactFields(vec![ + payment_types::ApplePayAddressParameters::PostalAddress, + ])); + + let required_shipping_contact_fields = + if business_profile.collect_shipping_details_from_wallet_connector == Some(true) { + let shipping_variants = enums::FieldType::get_shipping_variants(); + + is_dynamic_fields_required( + &state.conf.required_fields, + enums::PaymentMethod::Wallet, + enums::PaymentMethodType::ApplePay, + &connector.connector_name, + shipping_variants, + ) + .then_some(payment_types::ApplePayShippingContactFields(vec![ + payment_types::ApplePayAddressParameters::PostalAddress, + payment_types::ApplePayAddressParameters::Phone, + payment_types::ApplePayAddressParameters::Email, + ])) + } else { + None + }; // Get apple pay payment request let applepay_payment_request = get_apple_pay_payment_request( @@ -306,6 +333,7 @@ async fn create_applepay_session_token( apple_pay_session_request.merchant_identifier.as_str(), merchant_business_country, required_billing_contact_fields, + required_shipping_contact_fields, )?; let applepay_session_request = build_apple_pay_session_request( @@ -406,7 +434,8 @@ fn get_apple_pay_payment_request( session_data: types::PaymentsSessionData, merchant_identifier: &str, merchant_business_country: Option, - required_billing_contact_fields: Option>, + required_billing_contact_fields: Option, + required_shipping_contact_fields: Option, ) -> RouterResult { let applepay_payment_request = payment_types::ApplePayPaymentRequest { country_code: merchant_business_country.or(session_data.country).ok_or( @@ -420,6 +449,7 @@ fn get_apple_pay_payment_request( supported_networks: Some(payment_request_data.supported_networks), merchant_identifier: Some(merchant_identifier.to_string()), required_billing_contact_fields, + required_shipping_contact_fields, }; Ok(applepay_payment_request) } @@ -463,6 +493,7 @@ fn create_gpay_session_token( state: &routes::AppState, router_data: &types::PaymentsSessionRouterData, connector: &api::ConnectorData, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { let connector_metadata = router_data.connector_meta_data.clone(); let delayed_response = is_session_response_delayed(state, connector); @@ -546,6 +577,21 @@ fn create_gpay_session_token( })?, }; + let required_shipping_contact_fields = + if business_profile.collect_shipping_details_from_wallet_connector == Some(true) { + let shipping_variants = enums::FieldType::get_shipping_variants(); + + is_dynamic_fields_required( + &state.conf.required_fields, + enums::PaymentMethod::Wallet, + enums::PaymentMethodType::GooglePay, + &connector.connector_name, + shipping_variants, + ) + } else { + false + }; + Ok(types::PaymentsSessionRouterData { response: Ok(types::PaymentsResponseData::SessionResponse { session_token: payment_types::SessionToken::GooglePay(Box::new( @@ -560,6 +606,12 @@ fn create_gpay_session_token( }, delayed_session_token: false, secrets: None, + shipping_address_required: required_shipping_contact_fields, + email_required: required_shipping_contact_fields, + shipping_address_parameters: + api_models::payments::GpayShippingAddressParameters { + phone_number_required: required_shipping_contact_fields, + }, }, ), )), @@ -597,11 +649,14 @@ impl types::PaymentsSessionRouterData { connector: &api::ConnectorData, _confirm: Option, call_connector_action: payments::CallConnectorAction, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { match connector.get_token { - api::GetToken::GpayMetadata => create_gpay_session_token(state, self, connector), + api::GetToken::GpayMetadata => { + create_gpay_session_token(state, self, connector, business_profile) + } api::GetToken::ApplePayMetadata => { - create_applepay_session_token(state, self, connector).await + create_applepay_session_token(state, self, connector, business_profile).await } api::GetToken::Connector => { let connector_integration: services::BoxedConnectorIntegration< diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs index 59f7ee1755..cdadb355d4 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -11,7 +11,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, domain}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -55,6 +55,7 @@ impl Feature for types::Setup connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, connector_request: Option, + _business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult { let connector_integration: services::BoxedConnectorIntegration< '_, diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 1a39c06fb0..ebea9207b1 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -44,6 +44,7 @@ pub async fn do_gsm_actions( validate_result: &operations::ValidateResult<'_>, schedule_time: Option, frm_suggestion: Option, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult> where F: Clone + Send + Sync, @@ -95,6 +96,7 @@ where schedule_time, true, frm_suggestion, + business_profile, ) .await?; } @@ -140,6 +142,7 @@ where //this is an auto retry payment, but not step-up false, frm_suggestion, + business_profile, ) .await?; @@ -279,6 +282,7 @@ pub async fn do_retry( schedule_time: Option, is_step_up: bool, frm_suggestion: Option, + business_profile: &storage::business_profile::BusinessProfile, ) -> RouterResult> where F: Clone + Send + Sync, @@ -315,6 +319,7 @@ where schedule_time, api::HeaderPayload::default(), frm_suggestion, + business_profile, ) .await } diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 3449838164..894e09ab60 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -265,6 +265,7 @@ pub async fn update_business_profile_active_algorithm_ref( authentication_connector_details: None, extended_card_info_config: None, use_billing_as_payment_method_billing: None, + collect_shipping_details_from_wallet_connector: None, }; db.update_business_profile_by_profile_id(current_business_profile, business_profile_update) diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index 262999af94..fcbd97d5cb 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -182,6 +182,8 @@ impl ForeignTryFrom<(domain::MerchantAccount, BusinessProfileCreate)> use_billing_as_payment_method_billing: request .use_billing_as_payment_method_billing .or(Some(true)), + collect_shipping_details_from_wallet_connector: request + .collect_shipping_details_from_wallet_connector, }) } } diff --git a/migrations/2024-05-09-130152_collect_shipping_details_from_wallet_connector/down.sql b/migrations/2024-05-09-130152_collect_shipping_details_from_wallet_connector/down.sql new file mode 100644 index 0000000000..c2ffc7cff8 --- /dev/null +++ b/migrations/2024-05-09-130152_collect_shipping_details_from_wallet_connector/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE business_profile DROP COLUMN IF EXISTS collect_shipping_details_from_wallet_connector; \ No newline at end of file diff --git a/migrations/2024-05-09-130152_collect_shipping_details_from_wallet_connector/up.sql b/migrations/2024-05-09-130152_collect_shipping_details_from_wallet_connector/up.sql new file mode 100644 index 0000000000..f949c81213 --- /dev/null +++ b/migrations/2024-05-09-130152_collect_shipping_details_from_wallet_connector/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here + +ALTER TABLE business_profile ADD COLUMN IF NOT EXISTS collect_shipping_details_from_wallet_connector BOOLEAN DEFAULT FALSE; \ No newline at end of file diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 0c3893c463..efb084e16b 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -4968,6 +4968,20 @@ } ] }, + "ApplePayAddressParameters": { + "type": "string", + "enum": [ + "postalAddress", + "phone", + "email" + ] + }, + "ApplePayBillingContactFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplePayAddressParameters" + } + }, "ApplePayPaymentRequest": { "type": "object", "required": [ @@ -5006,11 +5020,19 @@ "nullable": true }, "required_billing_contact_fields": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The required billing contact fields for connector", + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayBillingContactFields" + } + ], + "nullable": true + }, + "required_shipping_contact_fields": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayShippingContactFields" + } + ], "nullable": true } } @@ -5033,6 +5055,12 @@ } ] }, + "ApplePayShippingContactFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplePayAddressParameters" + } + }, "ApplePayThirdPartySdkData": { "type": "object" }, @@ -6740,6 +6768,11 @@ "type": "boolean", "description": "Whether to use the billing details passed when creating the intent as payment method billing", "nullable": true + }, + "collect_shipping_details_from_wallet_connector": { + "type": "boolean", + "description": "A boolean value to indicate if cusomter shipping details needs to be sent for wallets payments", + "nullable": true } }, "additionalProperties": false @@ -9099,6 +9132,64 @@ } } }, + { + "type": "string", + "enum": [ + "user_shipping_name" + ] + }, + { + "type": "string", + "enum": [ + "user_shipping_address_line1" + ] + }, + { + "type": "string", + "enum": [ + "user_shipping_address_line2" + ] + }, + { + "type": "string", + "enum": [ + "user_shipping_address_city" + ] + }, + { + "type": "string", + "enum": [ + "user_shipping_address_pincode" + ] + }, + { + "type": "string", + "enum": [ + "user_shipping_address_state" + ] + }, + { + "type": "object", + "required": [ + "user_shipping_address_country" + ], + "properties": { + "user_shipping_address_country": { + "type": "object", + "required": [ + "options" + ], + "properties": { + "options": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, { "type": "string", "enum": [ @@ -9340,6 +9431,9 @@ "type": "object", "required": [ "merchant_info", + "shipping_address_required", + "email_required", + "shipping_address_parameters", "allowed_payment_methods", "transaction_info", "delayed_session_token", @@ -9350,6 +9444,17 @@ "merchant_info": { "$ref": "#/components/schemas/GpayMerchantInfo" }, + "shipping_address_required": { + "type": "boolean", + "description": "Is shipping address required" + }, + "email_required": { + "type": "boolean", + "description": "Is email required" + }, + "shipping_address_parameters": { + "$ref": "#/components/schemas/GpayShippingAddressParameters" + }, "allowed_payment_methods": { "type": "array", "items": { @@ -9536,6 +9641,18 @@ } ] }, + "GpayShippingAddressParameters": { + "type": "object", + "required": [ + "phone_number_required" + ], + "properties": { + "phone_number_required": { + "type": "boolean", + "description": "Is shipping phone number required" + } + } + }, "GpayTokenParameters": { "type": "object", "required": [