diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index dc0bd4ef07..592e5c7dea 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9684,6 +9684,23 @@ "GoPayRedirection": { "type": "object" }, + "GooglePayAssuranceDetails": { + "type": "object", + "required": [ + "card_holder_authenticated", + "account_verified" + ], + "properties": { + "card_holder_authenticated": { + "type": "boolean", + "description": "indicates that Cardholder possession validation has been performed" + }, + "account_verified": { + "type": "boolean", + "description": "indicates that identification and verifications (ID&V) was performed" + } + } + }, "GooglePayPaymentMethodInfo": { "type": "object", "required": [ @@ -9698,6 +9715,14 @@ "card_details": { "type": "string", "description": "The details of the card" + }, + "assurance_details": { + "allOf": [ + { + "$ref": "#/components/schemas/GooglePayAssuranceDetails" + } + ], + "nullable": true } } }, @@ -9845,6 +9870,11 @@ } ], "nullable": true + }, + "assurance_details_required": { + "type": "boolean", + "description": "Whether assurance details are required", + "nullable": true } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index d4acd6ae2a..498a0566e3 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2601,6 +2601,17 @@ pub struct GooglePayPaymentMethodInfo { pub card_network: String, /// The details of the card pub card_details: String, + //assurance_details of the card + pub assurance_details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct GooglePayAssuranceDetails { + ///indicates that Cardholder possession validation has been performed + pub card_holder_authenticated: bool, + /// indicates that identification and verifications (ID&V) was performed + pub account_verified: bool, } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -4098,6 +4109,8 @@ pub struct GpayAllowedMethodsParameters { /// Billing address parameters #[serde(skip_serializing_if = "Option::is_none")] pub billing_address_parameters: Option, + /// Whether assurance details are required + pub assurance_details_required: Option, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs index f6b1372b48..5003013ac9 100644 --- a/crates/connector_configs/src/transformer.rs +++ b/crates/connector_configs/src/transformer.rs @@ -316,6 +316,7 @@ impl DashboardRequestPayload { ], billing_address_required: None, billing_address_parameters: None, + assurance_details_required: Some(true), }; let allowed_payment_methods = payments::GpayAllowedPaymentMethods { payment_method_type: String::from("CARD"), diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index fbafd88961..1b62473b9a 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -311,6 +311,7 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, connector_transaction_id: Option, payment_method_data: Option, + authentication_type: Option, }, CaptureUpdate { amount_to_capture: Option, @@ -741,6 +742,7 @@ impl From for PaymentAttemptUpdateInternal { unified_message, connector_transaction_id, payment_method_data, + authentication_type, } => Self { connector: connector.map(Some), status: Some(status), @@ -754,6 +756,7 @@ impl From for PaymentAttemptUpdateInternal { unified_message, connector_transaction_id, payment_method_data, + authentication_type, ..Default::default() }, PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index b7305100dd..b4e6d2f3bc 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -206,6 +206,17 @@ pub struct GooglePayPaymentMethodInfo { pub card_network: String, /// The details of the card pub card_details: String, + //assurance_details of the card + pub assurance_details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GooglePayAssuranceDetails { + ///indicates that Cardholder possession validation has been performed + pub card_holder_authenticated: bool, + /// indicates that identification and verifications (ID&V) was performed + pub account_verified: bool, } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] @@ -623,6 +634,12 @@ impl From for GooglePayWalletData { info: GooglePayPaymentMethodInfo { card_network: value.info.card_network, card_details: value.info.card_details, + assurance_details: value.info.assurance_details.map(|info| { + GooglePayAssuranceDetails { + card_holder_authenticated: info.card_holder_authenticated, + account_verified: info.account_verified, + } + }), }, tokenization_data: GpayTokenizationData { token_type: value.tokenization_data.token_type, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index c36e29bfe6..d8b5288c85 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -414,6 +414,7 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, connector_transaction_id: Option, payment_method_data: Option, + authentication_type: Option, }, CaptureUpdate { amount_to_capture: Option, diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 603c6637aa..36b31fa282 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -493,6 +493,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::RetrievePaymentLinkResponse, api_models::payments::PaymentLinkInitiateRequest, api_models::payments::ExtendedCardInfoResponse, + api_models::payments::GooglePayAssuranceDetails, api_models::routing::RoutingConfigRequest, api_models::routing::RoutingDictionaryRecord, api_models::routing::RoutingKind, diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 1227191beb..b07b819f37 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -1123,6 +1123,7 @@ pub struct GpayTokenizationSpecification { pub struct GpayAllowedMethodsParameters { pub allowed_auth_methods: Vec, pub allowed_card_networks: Vec, + pub assurance_details_required: Option, } #[derive(Clone, Default, Debug, Deserialize, Serialize)] @@ -1343,6 +1344,7 @@ impl From for api_models::payments::GpayAllowedMet allowed_card_networks: value.allowed_card_networks, billing_address_required: None, billing_address_parameters: None, + assurance_details_required: value.assurance_details_required, } } } diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 263bba49d2..5909b0841f 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -246,6 +246,20 @@ pub trait RouterDataAuthorize { impl RouterDataAuthorize for types::PaymentsAuthorizeRouterData { fn decide_authentication_type(&mut self) { + if let hyperswitch_domain_models::payment_method_data::PaymentMethodData::Wallet( + hyperswitch_domain_models::payment_method_data::WalletData::GooglePay(google_pay_data), + ) = &self.request.payment_method_data + { + if let Some(assurance_details) = google_pay_data.info.assurance_details.as_ref() { + // Step up the transaction to 3DS when either assurance_details.card_holder_authenticated or assurance_details.account_verified is false + if !assurance_details.card_holder_authenticated + || !assurance_details.account_verified + { + logger::info!("Googlepay transaction stepped up to 3DS"); + self.auth_type = diesel_models::enums::AuthenticationType::ThreeDs; + } + } + } if self.auth_type == diesel_models::enums::AuthenticationType::ThreeDs && !self.request.enrolled_for_3ds { 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 f9618b5b9f..7a87677903 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use router_env::logger; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -50,7 +51,7 @@ impl #[async_trait] impl Feature for types::SetupMandateRouterData { async fn decide_flows<'a>( - self, + mut self, state: &SessionState, connector: &api::ConnectorData, call_connector_action: payments::CallConnectorAction, @@ -62,7 +63,21 @@ impl Feature for types::Setup types::SetupMandateRequestData, types::PaymentsResponseData, > = connector.connector.get_connector_integration(); - + // Change the authentication_type to ThreeDs, for google_pay wallet if card_holder_authenticated or account_verified in assurance_details is false + if let hyperswitch_domain_models::payment_method_data::PaymentMethodData::Wallet( + hyperswitch_domain_models::payment_method_data::WalletData::GooglePay(google_pay_data), + ) = &self.request.payment_method_data + { + if let Some(assurance_details) = google_pay_data.info.assurance_details.as_ref() { + // Step up the transaction to 3DS when either assurance_details.card_holder_authenticated or assurance_details.account_verified is false + if !assurance_details.card_holder_authenticated + || !assurance_details.account_verified + { + logger::info!("Googlepay transaction stepped up to 3DS"); + self.auth_type = diesel_models::enums::AuthenticationType::ThreeDs; + } + } + } let resp = services::execute_connector_processing_step( state, connector_integration, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index b7a1ffb7c5..c4e4cb694e 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -762,6 +762,13 @@ async fn payment_response_update_tracker( }); let (capture_update, mut payment_attempt_update) = match router_data.response.clone() { Err(err) => { + let auth_update = if Some(router_data.auth_type) + != payment_data.payment_attempt.authentication_type + { + Some(router_data.auth_type) + } else { + None + }; let (capture_update, attempt_update) = match payment_data.multiple_capture_data { Some(multiple_capture_data) => { let capture_update = storage::CaptureUpdate::ErrorUpdate { @@ -777,7 +784,15 @@ async fn payment_response_update_tracker( multiple_capture_data.get_latest_capture().clone(), capture_update, )]; - (Some((multiple_capture_data, capture_update_list)), None) + ( + Some((multiple_capture_data, capture_update_list)), + auth_update.map(|auth_type| { + storage::PaymentAttemptUpdate::AuthenticationTypeUpdate { + authentication_type: auth_type, + updated_by: storage_scheme.to_string(), + } + }), + ) } None => { let connector_name = router_data.connector.to_string(); @@ -835,6 +850,7 @@ async fn payment_response_update_tracker( unified_message: option_gsm.map(|gsm| gsm.unified_message), connector_transaction_id: err.connector_transaction_id, payment_method_data: additional_payment_method_data, + authentication_type: auth_update, }), ) } @@ -929,6 +945,14 @@ async fn payment_response_update_tracker( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Could not parse the connector response")?; + let auth_update = if Some(router_data.auth_type) + != payment_data.payment_attempt.authentication_type + { + Some(router_data.auth_type) + } else { + None + }; + // incase of success, update error code and error message let error_status = if router_data.status == enums::AttemptStatus::Charged { Some(None) @@ -965,7 +989,15 @@ async fn payment_response_update_tracker( multiple_capture_data.get_latest_capture().clone(), capture_update, )]; - (Some((multiple_capture_data, capture_update_list)), None) + ( + Some((multiple_capture_data, capture_update_list)), + auth_update.map(|auth_type| { + storage::PaymentAttemptUpdate::AuthenticationTypeUpdate { + authentication_type: auth_type, + updated_by: storage_scheme.to_string(), + } + }), + ) } None => ( None, @@ -973,7 +1005,7 @@ async fn payment_response_update_tracker( status: updated_attempt_status, connector: None, connector_transaction_id: connector_transaction_id.clone(), - authentication_type: None, + authentication_type: auth_update, amount_capturable: router_data .request .get_amount_capturable(&payment_data, updated_attempt_status) diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index a04af7b0fe..e8ceeaa7ec 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -423,6 +423,13 @@ where } Err(ref error_response) => { let option_gsm = get_gsm(state, &router_data).await?; + let auth_update = if Some(router_data.auth_type) + != payment_data.payment_attempt.authentication_type + { + Some(router_data.auth_type) + } else { + None + }; db.update_payment_attempt_with_attempt_id( payment_data.payment_attempt.clone(), @@ -438,6 +445,7 @@ where unified_message: option_gsm.map(|gsm| gsm.unified_message), connector_transaction_id: error_response.connector_transaction_id.clone(), payment_method_data: additional_payment_method_data, + authentication_type: auth_update, }, storage_scheme, ) diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 5e3223ec67..ef0fe07e9b 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -134,6 +134,7 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { unified_message: None, connector_transaction_id: None, payment_method_data: None, + authentication_type: None, }; payment_data.payment_attempt = db diff --git a/crates/router/tests/connectors/payu.rs b/crates/router/tests/connectors/payu.rs index 38bb892d0b..b0e5e9ec6a 100644 --- a/crates/router/tests/connectors/payu.rs +++ b/crates/router/tests/connectors/payu.rs @@ -96,6 +96,7 @@ async fn should_authorize_gpay_payment() { info: domain::GooglePayPaymentMethodInfo { card_network: "VISA".to_string(), card_details: "1234".to_string(), + assurance_details: None, }, tokenization_data: domain::GpayTokenizationData { token_type: "payu".to_string(), diff --git a/crates/router/tests/connectors/worldpay.rs b/crates/router/tests/connectors/worldpay.rs index fc0fde895e..d4f6e167ff 100644 --- a/crates/router/tests/connectors/worldpay.rs +++ b/crates/router/tests/connectors/worldpay.rs @@ -69,6 +69,7 @@ async fn should_authorize_gpay_payment() { info: domain::GooglePayPaymentMethodInfo { card_network: "VISA".to_string(), card_details: "1234".to_string(), + assurance_details: None, }, tokenization_data: domain::GpayTokenizationData { token_type: "worldpay".to_string(), diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 70cb60f521..4828c8b370 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1650,6 +1650,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_message, connector_transaction_id, payment_method_data, + authentication_type, } => DieselPaymentAttemptUpdate::ErrorUpdate { connector, status, @@ -1663,6 +1664,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_message, connector_transaction_id, payment_method_data, + authentication_type, }, Self::CaptureUpdate { multiple_capture_count, @@ -1981,6 +1983,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_message, connector_transaction_id, payment_method_data, + authentication_type, } => Self::ErrorUpdate { connector, status, @@ -1993,6 +1996,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_message, connector_transaction_id, payment_method_data, + authentication_type, }, DieselPaymentAttemptUpdate::CaptureUpdate { amount_to_capture,