Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(payment_methods): pass required_billing_contact_fields field in /session call based on dynamic fields #4601

Merged
merged 23 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a0788dc
feat(payment_methods): pass field in apple pay seesion call based on…
ShankarSinghC May 9, 2024
fce78e1
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] May 9, 2024
706ae12
check for mandate and common dynamic fields as well
ShankarSinghC May 9, 2024
f1d8698
address pr comment
ShankarSinghC May 9, 2024
015b6a9
chore: run formatter
hyperswitch-bot[bot] May 9, 2024
d98c377
Merge branch 'main' into apple_pay/billing-dynamic-fields
ShankarSinghC May 9, 2024
704130d
address failing checks
ShankarSinghC May 9, 2024
4695eac
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] May 9, 2024
10cc11c
address pr comment
ShankarSinghC May 9, 2024
1b9bd5b
Merge branch 'apple_pay/billing-dynamic-fields' of https://github.com…
ShankarSinghC May 9, 2024
7751a00
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] May 9, 2024
d4e7145
feat(payment_methods): pass field in gpay pay seesion call based on …
ShankarSinghC May 10, 2024
00a0a07
Merge branch 'apple_pay/billing-dynamic-fields' of https://github.com…
ShankarSinghC May 10, 2024
7927eff
address pr comments
ShankarSinghC May 10, 2024
e765890
Merge branch 'main' into apple_pay/billing-dynamic-fields
ShankarSinghC May 10, 2024
fe7ac8a
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] May 10, 2024
67dbf20
add a tests case
ShankarSinghC May 13, 2024
69d38df
Merge branch 'main' of https://github.com/juspay/hyperswitch into app…
ShankarSinghC May 13, 2024
65352ac
fix typos
ShankarSinghC May 13, 2024
f824032
fix clippy errors
ShankarSinghC May 13, 2024
7658591
make format firld as enum
ShankarSinghC May 13, 2024
6172632
Merge branch 'main' of https://github.com/juspay/hyperswitch into app…
ShankarSinghC May 13, 2024
c71977b
address failing checks
ShankarSinghC May 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
85 changes: 83 additions & 2 deletions crates/api_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -430,6 +428,89 @@ pub enum FieldType {
DropDown { options: Vec<String> },
}

impl FieldType {
pub fn get_billing_variants() -> Vec<Self> {
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 {
ShankarSinghC marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand Down
2 changes: 1 addition & 1 deletion crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ impl From<Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>> 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,
Expand Down
20 changes: 20 additions & 0 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3867,6 +3867,24 @@ pub struct GpayAllowedMethodsParameters {
pub allowed_auth_methods: Vec<String>,
/// The list of allowed card networks (ex: AMEX,JCB etc)
pub allowed_card_networks: Vec<String>,
/// Is billing address required
pub billing_address_required: Option<bool>,
/// Billing address parameters
pub billing_address_parameters: Option<GpayBillingAddressParameters>,
}

#[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)]
Expand Down Expand Up @@ -4213,6 +4231,8 @@ pub struct ApplePayPaymentRequest {
/// The list of supported networks
pub supported_networks: Option<Vec<String>>,
pub merchant_identifier: Option<String>,
/// The required billing contact fields for connector
pub required_billing_contact_fields: Option<Vec<String>>,
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)]
Expand Down
2 changes: 2 additions & 0 deletions crates/connector_configs/src/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
2 changes: 2 additions & 0 deletions crates/openapi/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/connector/bluesnap/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
merchant_capabilities: Some(payment_request_data.merchant_capabilities),
supported_networks: Some(payment_request_data.supported_networks),
merchant_identifier: Some(session_token_data.merchant_identifier),
required_billing_contact_fields: None,
}),
connector: "bluesnap".to_string(),
delayed_session_token: false,
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/connector/payme/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ impl<F>
merchant_capabilities: None,
supported_networks: None,
merchant_identifier: None,
required_billing_contact_fields: None,
},
),
connector: "payme".to_string(),
Expand Down
3 changes: 3 additions & 0 deletions crates/router/src/connector/trustpay/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,7 @@ pub fn get_apple_pay_session<F, T>(
),
total: apple_pay_init_result.total.into(),
merchant_identifier: None,
required_billing_contact_fields: None,
}),
connector: "trustpay".to_string(),
delayed_session_token: true,
Expand Down Expand Up @@ -1326,6 +1327,8 @@ impl From<GpayAllowedMethodsParameters> 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,
}
}
}
Expand Down
92 changes: 89 additions & 3 deletions crates/router/src/core/payments/flows/session_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -78,6 +82,39 @@ impl Feature<api::Session, types::PaymentsSessionData> 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<enums::FieldType>,
) -> 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<common_utils::pii::SecretSerdeValue>,
) -> RouterResult<payment_types::ApplepaySessionTokenMetadata> {
Expand Down Expand Up @@ -247,13 +284,28 @@ 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,
payment_request_data,
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(
Expand Down Expand Up @@ -354,6 +406,7 @@ fn get_apple_pay_payment_request(
session_data: types::PaymentsSessionData,
merchant_identifier: &str,
merchant_business_country: Option<api_models::enums::CountryAlpha2>,
required_billing_contact_fields: Option<Vec<String>>,
) -> RouterResult<payment_types::ApplePayPaymentRequest> {
let applepay_payment_request = payment_types::ApplePayPaymentRequest {
country_code: merchant_business_country.or(session_data.country).ok_or(
Expand All @@ -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)
}
Expand Down Expand Up @@ -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(),
Expand All @@ -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 {
Expand Down