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(core): confirm flow and authorization api changes for external authentication #4015

Merged
merged 38 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
109e34a
feat(core): separate authentication related schema changes for existi…
hrithikesh026 Feb 29, 2024
f2034c4
Merge branch 'main' into authn-schema-changes-for-other-tables
hrithikesh026 Mar 4, 2024
51edecc
chore: update Cargo.lock
hyperswitch-bot[bot] Mar 4, 2024
3b699a6
fix: authentication_connector_details to business_profile from mercha…
hrithikesh026 Mar 4, 2024
2ea66d8
Merge branch 'main' into authn-schema-changes-for-other-tables
hrithikesh026 Mar 4, 2024
2e61580
chore: address cargo hack failure
hrithikesh026 Mar 4, 2024
95f5c0f
Merge branch 'main' into authn-schema-changes-for-other-tables
hrithikesh026 Mar 5, 2024
5d6c48c
add other prerequisites for 3ds external authentication
sai-harsha-vardhan Mar 5, 2024
fb9eaf1
resolve conflicts
sai-harsha-vardhan Mar 5, 2024
b7e73cf
feat(core): add core functions for external authentication
hrithikesh026 Mar 5, 2024
1d9d51d
resolve conflicts
sai-harsha-vardhan Mar 6, 2024
915946a
Merge branch 'add-other-prerequisites-for-external-authn' into core-m…
sai-harsha-vardhan Mar 6, 2024
3c8713d
Merge branch 'main' into add-other-prerequisites-for-external-authn
sai-harsha-vardhan Mar 6, 2024
da51b0b
Merge branch 'add-other-prerequisites-for-external-authn' into core-m…
sai-harsha-vardhan Mar 6, 2024
4c3ea4c
feat(core): confirm flow and authorization api changes for external a…
hrithikesh026 Mar 7, 2024
977a6e0
address cargo hack failure
hrithikesh026 Mar 8, 2024
3edf689
address comments
hrithikesh026 Mar 8, 2024
3843560
don't allow request_external_three_ds_authentication update in confir…
hrithikesh026 Mar 8, 2024
a221d15
Merge branch 'main' into confirm-and-authorization-api-changes
hrithikesh026 Mar 8, 2024
cafeb70
Merge branch 'main' into confirm-and-authorization-api-changes
hrithikesh026 Mar 10, 2024
56bf618
match unmatched enum
hrithikesh026 Mar 10, 2024
6e5fed9
chore: update Cargo.lock
hyperswitch-bot[bot] Mar 10, 2024
80a3641
no fallback if preauth call fails
hrithikesh026 Mar 11, 2024
6396aff
return next action object if external authentication
hrithikesh026 Mar 11, 2024
b063eb9
restrict PaymentSource enum types for internal use
hrithikesh026 Mar 11, 2024
ccd6bb0
move is_separate_authentication_supported function to connector enum …
hrithikesh026 Mar 11, 2024
31dee6e
address comments
hrithikesh026 Mar 11, 2024
252770a
Merge branch 'main' into confirm-and-authorization-api-changes
hrithikesh026 Mar 11, 2024
04923e9
chore: update Cargo.lock
hyperswitch-bot[bot] Mar 11, 2024
a4a6583
refactor update_trackers in payment confirm
hrithikesh026 Mar 11, 2024
6b64290
fix compilation failure
hrithikesh026 Mar 11, 2024
a7659e0
Merge branch 'main' into confirm-and-authorization-api-changes
hrithikesh026 Mar 11, 2024
a3ca7b1
address comments
hrithikesh026 Mar 11, 2024
69fad2b
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Mar 11, 2024
e4046d3
chore: add threedsecureio base url in deployment files
hrithikesh026 Mar 11, 2024
8887a03
use profile_id from business_profile instead of payment_intent
hrithikesh026 Mar 12, 2024
3013d8e
Merge branch 'main' into confirm-and-authorization-api-changes
hrithikesh026 Mar 12, 2024
6486e00
update production url in deployment config files
hrithikesh026 Mar 12, 2024
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
13 changes: 7 additions & 6 deletions crates/cards/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ impl FromStr for CardNumber {
type Err = CCValError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match luhn::valid(s) {
true => {
let cc_no_whitespace: String = s.split_whitespace().collect();
Ok(Self(StrongSecret::from_str(&cc_no_whitespace)?))
}
false => Err(CCValError),
// Valid test cards for threedsecureio
let valid_test_cards = ["4000100511112003", "6000100611111203", "3000100811111072"];
if luhn::valid(s) || valid_test_cards.contains(&s) {
let cc_no_whitespace: String = s.split_whitespace().collect();
Ok(Self(StrongSecret::from_str(&cc_no_whitespace)?))
} else {
Err(CCValError)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2100,6 +2100,7 @@ pub enum PaymentSource {
Dashboard,
Sdk,
Webhook,
ExternalAuthenticator,
}

#[derive(
Expand Down
1 change: 1 addition & 0 deletions crates/diesel_models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod capture;
pub mod cards_info;
pub mod configs;

pub mod authentication;
pub mod authorization;
pub mod blocklist;
pub mod blocklist_fingerprint;
Expand Down
1 change: 1 addition & 0 deletions crates/diesel_models/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod capture;
pub mod cards_info;
pub mod configs;

pub mod authentication;
pub mod authorization;
pub mod blocklist;
pub mod blocklist_fingerprint;
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,7 @@ pub const MIN_SESSION_EXPIRY: u32 = 60;

pub const LOCKER_HEALTH_CALL_PATH: &str = "/health";

pub const AUTHENTICATION_ID_PREFIX: &str = "authn";

// URL for checking the outgoing call
pub const OUTGOING_CALL_URL: &str = "https://api.stripe.com/healthcheck";
1 change: 1 addition & 0 deletions crates/router/src/core.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod admin;
pub mod api_keys;
pub mod api_locking;
pub mod authentication;
pub mod blocklist;
pub mod cache;
pub mod cards_info;
Expand Down
18 changes: 17 additions & 1 deletion crates/router/src/core/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,8 @@ pub async fn create_payment_connector(

let pm_auth_connector =
api_enums::convert_pm_auth_connector(req.connector_name.to_string().as_str());
let authentication_connector =
api_enums::convert_authentication_connector(req.connector_name.to_string().as_str());

if pm_auth_connector.is_some() {
if req.connector_type != api_enums::ConnectorType::PaymentMethodAuth {
Expand All @@ -790,6 +792,13 @@ pub async fn create_payment_connector(
})
.into_report();
}
} else if authentication_connector.is_some() {
if req.connector_type != api_enums::ConnectorType::AuthenticationProcessor {
return Err(errors::ApiErrorResponse::InvalidRequestData {
message: "Invalid connector type given".to_string(),
})
.into_report();
}
} else {
let routable_connector_option = req
.connector_name
Expand Down Expand Up @@ -1657,7 +1666,14 @@ pub async fn update_business_profile(
applepay_verified_domains: request.applepay_verified_domains,
payment_link_config,
session_expiry: request.session_expiry.map(i64::from),
authentication_connector_details: None,
authentication_connector_details: request
.authentication_connector_details
.as_ref()
.map(Encode::encode_to_value)
.transpose()
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "authentication_connector_details",
})?,
};

let updated_business_profile = db
Expand Down
222 changes: 222 additions & 0 deletions crates/router/src/core/authentication.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
pub(crate) mod utils;

pub mod transformers;
pub mod types;

use api_models::payments;
use common_enums::Currency;
use common_utils::{errors::CustomResult, ext_traits::ValueExt};
use error_stack::ResultExt;
use masking::PeekInterface;

use super::errors;
use crate::{
core::{errors::ApiErrorResponse, payments as payments_core},
routes::AppState,
types::{self as core_types, api, authentication::AuthenticationResponseData, storage},
utils::OptionExt,
};

#[allow(clippy::too_many_arguments)]
pub async fn perform_authentication(
state: &AppState,
authentication_connector: String,
payment_method_data: payments::PaymentMethodData,
payment_method: common_enums::PaymentMethod,
billing_address: api_models::payments::Address,
shipping_address: Option<api_models::payments::Address>,
browser_details: Option<core_types::BrowserInformation>,
business_profile: core_types::storage::BusinessProfile,
merchant_connector_account: payments_core::helpers::MerchantConnectorAccountType,
amount: Option<i64>,
currency: Option<Currency>,
message_category: api::authentication::MessageCategory,
device_channel: payments::DeviceChannel,
authentication_data: (types::AuthenticationData, storage::Authentication),
return_url: Option<String>,
sdk_information: Option<payments::SdkInformation>,
threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator,
email: Option<common_utils::pii::Email>,
) -> CustomResult<core_types::api::authentication::AuthenticationResponse, ApiErrorResponse> {
let router_data = transformers::construct_authentication_router_data(
authentication_connector.clone(),
payment_method_data,
payment_method,
billing_address,
shipping_address,
browser_details,
amount,
currency,
message_category,
device_channel,
business_profile,
merchant_connector_account,
authentication_data.clone(),
return_url,
sdk_information,
threeds_method_comp_ind,
email,
)?;
let response =
utils::do_auth_connector_call(state, authentication_connector.clone(), router_data).await?;
let (_authentication, _authentication_data) =
utils::update_trackers(state, response.clone(), authentication_data.1, None, None).await?;
let authentication_response =
response
.response
.map_err(|err| ApiErrorResponse::ExternalConnectorError {
code: err.code,
message: err.message,
connector: authentication_connector,
status_code: err.status_code,
reason: err.reason,
})?;
match authentication_response {
AuthenticationResponseData::AuthNResponse {
authn_flow_type,
trans_status,
..
} => Ok(match authn_flow_type {
core_types::authentication::AuthNFlowType::Challenge(challenge_params) => {
core_types::api::AuthenticationResponse {
trans_status,
acs_url: challenge_params.acs_url,
challenge_request: challenge_params.challenge_request,
acs_reference_number: challenge_params.acs_reference_number,
acs_trans_id: challenge_params.acs_trans_id,
three_dsserver_trans_id: challenge_params.three_dsserver_trans_id,
acs_signed_content: challenge_params.acs_signed_content,
}
}
core_types::authentication::AuthNFlowType::Frictionless => {
core_types::api::AuthenticationResponse {
trans_status,
acs_url: None,
challenge_request: None,
acs_reference_number: None,
acs_trans_id: None,
three_dsserver_trans_id: None,
acs_signed_content: None,
}
}
}),
_ => Err(errors::ApiErrorResponse::InternalServerError.into())
.attach_printable("unexpected response in authentication flow")?,
}
}

pub async fn perform_post_authentication<F: Clone + Send>(
state: &AppState,
authentication_connector: String,
business_profile: core_types::storage::BusinessProfile,
merchant_connector_account: payments_core::helpers::MerchantConnectorAccountType,
authentication_flow_input: types::PostAuthenthenticationFlowInput<'_, F>,
) -> CustomResult<(), ApiErrorResponse> {
match authentication_flow_input {
types::PostAuthenthenticationFlowInput::PaymentAuthNFlow {
payment_data,
authentication_data,
should_continue_confirm_transaction,
} => {
let new_authentication_data = if !authentication_data
.0
.authentication_status
.is_terminal_status()
{
let router_data = transformers::construct_post_authentication_router_data(
authentication_connector.clone(),
business_profile,
merchant_connector_account,
authentication_data.1,
)?;
let router_data =
utils::do_auth_connector_call(state, authentication_connector, router_data)
.await?;
let updated_authentication = utils::update_trackers(
state,
router_data,
authentication_data.0,
payment_data.token.clone(),
None,
)
.await?;
payment_data.authentication = Some(updated_authentication.clone());
updated_authentication
} else {
authentication_data
};
//If authentication is not successful, skip the payment connector flows and mark the payment as failure
if !(new_authentication_data.0.authentication_status
== api_models::enums::AuthenticationStatus::Success)
{
*should_continue_confirm_transaction = false;
}
}
types::PostAuthenthenticationFlowInput::PaymentMethodAuthNFlow { other_fields: _ } => {
// todo!("Payment method post authN operation");
}
}
Ok(())
}

pub async fn perform_pre_authentication<F: Clone + Send>(
state: &AppState,
authentication_connector_name: String,
authentication_flow_input: types::PreAuthenthenticationFlowInput<'_, F>,
business_profile: &core_types::storage::BusinessProfile,
three_ds_connector_account: payments_core::helpers::MerchantConnectorAccountType,
payment_connector_account: payments_core::helpers::MerchantConnectorAccountType,
) -> CustomResult<(), ApiErrorResponse> {
let authentication = utils::create_new_authentication(
state,
business_profile.merchant_id.clone(),
authentication_connector_name.clone(),
)
.await?;
match authentication_flow_input {
types::PreAuthenthenticationFlowInput::PaymentAuthNFlow {
payment_data,
should_continue_confirm_transaction,
card_number,
} => {
let router_data = transformers::construct_pre_authentication_router_data(
authentication_connector_name.clone(),
card_number,
&three_ds_connector_account,
business_profile.merchant_id.clone(),
)?;
let router_data =
utils::do_auth_connector_call(state, authentication_connector_name, router_data)
.await?;
let acquirer_details: types::AcquirerDetails = payment_connector_account
.get_metadata()
.get_required_value("merchant_connector_account.metadata")?
.peek()
.clone()
.parse_value("AcquirerDetails")
.change_context(ApiErrorResponse::PreconditionFailed { message: "acquirer_bin and acquirer_merchant_id not found in Payment Connector's Metadata".to_string()})?;

let (authentication, authentication_data) = utils::update_trackers(
state,
router_data,
authentication,
payment_data.token.clone(),
Some(acquirer_details),
)
.await?;
if authentication_data.is_separate_authn_required()
|| authentication.authentication_status.is_failed()
{
*should_continue_confirm_transaction = false;
}
payment_data.authentication = Some((authentication, authentication_data))
}
types::PreAuthenthenticationFlowInput::PaymentMethodAuthNFlow {
card_number: _,
other_fields: _,
} => {
// todo!("Payment method authN operation");
}
};
Ok(())
}
Loading
Loading