mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
feat(payment_methods): pass required_billing_contact_fields field in /session call based on dynamic fields (#4601)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -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<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 {
|
||||
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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -3869,6 +3869,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)]
|
||||
@ -4215,6 +4233,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)]
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -550,6 +550,7 @@ impl<F>
|
||||
merchant_capabilities: None,
|
||||
supported_networks: None,
|
||||
merchant_identifier: None,
|
||||
required_billing_contact_fields: None,
|
||||
},
|
||||
),
|
||||
connector: "payme".to_string(),
|
||||
|
||||
@ -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,
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<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> {
|
||||
@ -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<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(
|
||||
@ -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 {
|
||||
|
||||
@ -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": [
|
||||
|
||||
Reference in New Issue
Block a user