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(router): add support for googlepay step up flow #2744

Merged
merged 16 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -248,6 +248,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;
sai-harsha-vardhan marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a comment here to inform that 3ds has been stepped up

}
}
}
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 @@ -63,7 +64,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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, can you add a comment here to inform that 3ds has been stepped up?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, can you add a comment here to inform that 3ds has been stepped up?

I meant logs in this case

}
}
}
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
Loading