Skip to content

Commit

Permalink
feat(router): add support for googlepay step up flow (#2744)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Prasunna Soppa <[email protected]>
Co-authored-by: Prasunna Soppa <[email protected]>
Co-authored-by: sai-harsha-vardhan <[email protected]>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
6 people authored Jun 24, 2024
1 parent b7bf457 commit ff84d78
Show file tree
Hide file tree
Showing 16 changed files with 149 additions and 5 deletions.
30 changes: 30 additions & 0 deletions api-reference/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand All @@ -9698,6 +9715,14 @@
"card_details": {
"type": "string",
"description": "The details of the card"
},
"assurance_details": {
"allOf": [
{
"$ref": "#/components/schemas/GooglePayAssuranceDetails"
}
],
"nullable": true
}
}
},
Expand Down Expand Up @@ -9845,6 +9870,11 @@
}
],
"nullable": true
},
"assurance_details_required": {
"type": "boolean",
"description": "Whether assurance details are required",
"nullable": true
}
}
},
Expand Down
13 changes: 13 additions & 0 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GooglePayAssuranceDetails>,
}

#[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)]
Expand Down Expand Up @@ -4098,6 +4109,8 @@ pub struct GpayAllowedMethodsParameters {
/// Billing address parameters
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address_parameters: Option<GpayBillingAddressParameters>,
/// Whether assurance details are required
pub assurance_details_required: Option<bool>,
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
Expand Down
1 change: 1 addition & 0 deletions crates/connector_configs/src/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
3 changes: 3 additions & 0 deletions crates/diesel_models/src/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ pub enum PaymentAttemptUpdate {
unified_message: Option<Option<String>>,
connector_transaction_id: Option<String>,
payment_method_data: Option<serde_json::Value>,
authentication_type: Option<storage_enums::AuthenticationType>,
},
CaptureUpdate {
amount_to_capture: Option<i64>,
Expand Down Expand Up @@ -741,6 +742,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
unified_message,
connector_transaction_id,
payment_method_data,
authentication_type,
} => Self {
connector: connector.map(Some),
status: Some(status),
Expand All @@ -754,6 +756,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
unified_message,
connector_transaction_id,
payment_method_data,
authentication_type,
..Default::default()
},
PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self {
Expand Down
17 changes: 17 additions & 0 deletions crates/hyperswitch_domain_models/src/payment_method_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GooglePayAssuranceDetails>,
}

#[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)]
Expand Down Expand Up @@ -623,6 +634,12 @@ impl From<api_models::payments::GooglePayWalletData> 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ pub enum PaymentAttemptUpdate {
unified_message: Option<Option<String>>,
connector_transaction_id: Option<String>,
payment_method_data: Option<serde_json::Value>,
authentication_type: Option<storage_enums::AuthenticationType>,
},
CaptureUpdate {
amount_to_capture: Option<MinorUnit>,
Expand Down
1 change: 1 addition & 0 deletions crates/openapi/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/connector/trustpay/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions crates/router/src/core/payments/flows/authorize_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
19 changes: 17 additions & 2 deletions crates/router/src/core/payments/flows/setup_mandate_flow.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use async_trait::async_trait;
use router_env::logger;

use super::{ConstructFlowSpecificData, Feature};
use crate::{
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
38 changes: 35 additions & 3 deletions crates/router/src/core/payments/operations/payment_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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();
Expand Down Expand Up @@ -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,
}),
)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -965,15 +989,23 @@ 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,
Some(storage::PaymentAttemptUpdate::ResponseUpdate {
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)
Expand Down
8 changes: 8 additions & 0 deletions crates/router/src/core/payments/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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,
)
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/workflows/payment_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions crates/router/tests/connectors/payu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions crates/router/tests/connectors/worldpay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
4 changes: 4 additions & 0 deletions crates/storage_impl/src/payments/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1650,6 +1650,7 @@ impl DataModelExt for PaymentAttemptUpdate {
unified_message,
connector_transaction_id,
payment_method_data,
authentication_type,
} => DieselPaymentAttemptUpdate::ErrorUpdate {
connector,
status,
Expand All @@ -1663,6 +1664,7 @@ impl DataModelExt for PaymentAttemptUpdate {
unified_message,
connector_transaction_id,
payment_method_data,
authentication_type,
},
Self::CaptureUpdate {
multiple_capture_count,
Expand Down Expand Up @@ -1981,6 +1983,7 @@ impl DataModelExt for PaymentAttemptUpdate {
unified_message,
connector_transaction_id,
payment_method_data,
authentication_type,
} => Self::ErrorUpdate {
connector,
status,
Expand All @@ -1993,6 +1996,7 @@ impl DataModelExt for PaymentAttemptUpdate {
unified_message,
connector_transaction_id,
payment_method_data,
authentication_type,
},
DieselPaymentAttemptUpdate::CaptureUpdate {
amount_to_capture,
Expand Down

0 comments on commit ff84d78

Please sign in to comment.