feat(router): add support for googlepay step up flow (#2744)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Prasunna Soppa <prasunna.soppa@juspay.in>
Co-authored-by: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com>
Co-authored-by: sai-harsha-vardhan <harsha111hero@gmail.com>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Shanks
2024-06-24 19:39:50 +05:30
committed by GitHub
parent b7bf457d0c
commit ff84d78c65
16 changed files with 149 additions and 5 deletions

View File

@ -1123,6 +1123,7 @@ pub struct GpayTokenizationSpecification {
pub struct GpayAllowedMethodsParameters {
pub allowed_auth_methods: Vec<String>,
pub allowed_card_networks: Vec<String>,
pub assurance_details_required: Option<bool>,
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
@ -1343,6 +1344,7 @@ impl From<GpayAllowedMethodsParameters> 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,
}
}
}

View File

@ -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
{

View File

@ -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<api::SetupMandate, types::SetupMandateRequestData> 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<api::SetupMandate, types::SetupMandateRequestData> 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,

View File

@ -762,6 +762,13 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
});
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<F: Clone, T: types::Capturable>(
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<F: Clone, T: types::Capturable>(
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<F: Clone, T: types::Capturable>(
.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<F: Clone, T: types::Capturable>(
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<F: Clone, T: types::Capturable>(
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)

View File

@ -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,
)

View File

@ -134,6 +134,7 @@ impl ProcessTrackerWorkflow<SessionState> for PaymentsSyncWorkflow {
unified_message: None,
connector_transaction_id: None,
payment_method_data: None,
authentication_type: None,
};
payment_data.payment_attempt = db

View File

@ -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(),

View File

@ -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(),