mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 13:30:39 +08:00
feat(apple_pay): add support for pre decrypted apple pay token (#2056)
Co-authored-by: Sangamesh <sangamesh.kulkarni@juspay.in>
This commit is contained in:
@ -13,6 +13,8 @@ use common_utils::{ext_traits::AsyncExt, pii};
|
||||
use diesel_models::{ephemeral_key, fraud_check::FraudCheck};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use futures::future::join_all;
|
||||
#[cfg(feature = "kms")]
|
||||
use helpers::ApplePayData;
|
||||
use masking::Secret;
|
||||
use router_env::{instrument, tracing};
|
||||
use scheduler::{db::process_tracker::ProcessTrackerExt, errors as sch_errors, utils as pt_utils};
|
||||
@ -133,9 +135,13 @@ where
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let (mut payment_data, tokenization_action) =
|
||||
get_connector_tokenization_action(state, &operation, payment_data, &validate_result)
|
||||
.await?;
|
||||
payment_data = tokenize_in_router_when_confirm_false(
|
||||
state,
|
||||
&operation,
|
||||
&mut payment_data,
|
||||
&validate_result,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let updated_customer = call_create_connector_customer_if_required(
|
||||
state,
|
||||
@ -160,9 +166,8 @@ where
|
||||
&mut payment_data,
|
||||
&customer,
|
||||
call_connector_action,
|
||||
tokenization_action,
|
||||
updated_customer,
|
||||
validate_result.requeue,
|
||||
&validate_result,
|
||||
schedule_time,
|
||||
)
|
||||
.await?;
|
||||
@ -547,9 +552,8 @@ pub async fn call_connector_service<F, RouterDReq, ApiRequest>(
|
||||
payment_data: &mut PaymentData<F>,
|
||||
customer: &Option<domain::Customer>,
|
||||
call_connector_action: CallConnectorAction,
|
||||
tokenization_action: TokenizationAction,
|
||||
updated_customer: Option<storage::CustomerUpdate>,
|
||||
requeue: bool,
|
||||
validate_result: &operations::ValidateResult<'_>,
|
||||
schedule_time: Option<time::PrimitiveDateTime>,
|
||||
) -> RouterResult<router_types::RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
|
||||
where
|
||||
@ -567,6 +571,33 @@ where
|
||||
{
|
||||
let stime_connector = Instant::now();
|
||||
|
||||
let pm_data = payment_data.clone();
|
||||
|
||||
let connector_name = pm_data
|
||||
.payment_attempt
|
||||
.connector
|
||||
.as_ref()
|
||||
.get_required_value("connector")?;
|
||||
|
||||
let merchant_connector_account = construct_connector_label_and_get_mca(
|
||||
state,
|
||||
merchant_account,
|
||||
payment_data,
|
||||
connector_name,
|
||||
key_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (mut payment_data, tokenization_action) =
|
||||
get_connector_tokenization_action_when_confirm_true(
|
||||
state,
|
||||
operation,
|
||||
payment_data,
|
||||
validate_result,
|
||||
&merchant_connector_account,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut router_data = payment_data
|
||||
.construct_router_data(
|
||||
state,
|
||||
@ -574,6 +605,7 @@ where
|
||||
merchant_account,
|
||||
key_store,
|
||||
customer,
|
||||
&merchant_connector_account,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -587,18 +619,50 @@ where
|
||||
&call_connector_action,
|
||||
);
|
||||
|
||||
// Tokenization Action will be DecryptApplePayToken, only when payment method type is Apple Pay
|
||||
// and the connector supports Apple Pay predecrypt
|
||||
#[cfg(feature = "kms")]
|
||||
if matches!(
|
||||
tokenization_action,
|
||||
TokenizationAction::DecryptApplePayToken
|
||||
| TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt
|
||||
) {
|
||||
let apple_pay_data = match payment_data.payment_method_data.clone() {
|
||||
Some(api_models::payments::PaymentMethodData::Wallet(
|
||||
api_models::payments::WalletData::ApplePay(wallet_data),
|
||||
)) => Some(
|
||||
ApplePayData::token_json(api_models::payments::WalletData::ApplePay(wallet_data))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.decrypt(state)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?,
|
||||
),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let apple_pay_predecrypt = apple_pay_data
|
||||
.parse_value::<router_types::ApplePayPredecryptData>("ApplePayPredecryptData")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
router_data.payment_method_token = Some(router_types::PaymentMethodToken::ApplePayDecrypt(
|
||||
Box::new(apple_pay_predecrypt),
|
||||
));
|
||||
}
|
||||
|
||||
let pm_token = router_data
|
||||
.add_payment_method_token(state, &connector, &tokenization_action)
|
||||
.await?;
|
||||
|
||||
if let Some(payment_method_token) = pm_token {
|
||||
router_data.payment_method_token = Some(payment_method_token);
|
||||
if let Some(payment_method_token) = pm_token.clone() {
|
||||
router_data.payment_method_token = Some(router_types::PaymentMethodToken::Token(
|
||||
payment_method_token,
|
||||
));
|
||||
};
|
||||
|
||||
(router_data, should_continue_further) = complete_preprocessing_steps_if_required(
|
||||
state,
|
||||
&connector,
|
||||
payment_data,
|
||||
&payment_data,
|
||||
router_data,
|
||||
operation,
|
||||
should_continue_further,
|
||||
@ -624,13 +688,13 @@ where
|
||||
(None, false)
|
||||
};
|
||||
|
||||
if should_add_task_to_process_tracker(payment_data) {
|
||||
if should_add_task_to_process_tracker(&payment_data) {
|
||||
operation
|
||||
.to_domain()?
|
||||
.add_task_to_process_tracker(
|
||||
state,
|
||||
&payment_data.payment_attempt,
|
||||
requeue,
|
||||
validate_result.requeue,
|
||||
schedule_time,
|
||||
)
|
||||
.await
|
||||
@ -641,7 +705,7 @@ where
|
||||
// Update the payment trackers just before calling the connector
|
||||
// Since the request is already built in the previous step,
|
||||
// there should be no error in request construction from hyperswitch end
|
||||
(_, *payment_data) = operation
|
||||
(_, payment_data) = operation
|
||||
.to_update_tracker()?
|
||||
.update_trackers(
|
||||
&*state.store,
|
||||
@ -711,8 +775,25 @@ where
|
||||
|
||||
for session_connector_data in connectors.iter() {
|
||||
let connector_id = session_connector_data.connector.connector.id();
|
||||
|
||||
let merchant_connector_account = construct_connector_label_and_get_mca(
|
||||
state,
|
||||
merchant_account,
|
||||
&mut payment_data,
|
||||
&session_connector_data.connector.connector_name.to_string(),
|
||||
key_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let router_data = payment_data
|
||||
.construct_router_data(state, connector_id, merchant_account, key_store, customer)
|
||||
.construct_router_data(
|
||||
state,
|
||||
connector_id,
|
||||
merchant_account,
|
||||
key_store,
|
||||
customer,
|
||||
&merchant_connector_account,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let res = router_data.decide_flows(
|
||||
@ -793,6 +874,15 @@ where
|
||||
|
||||
match connector_name {
|
||||
Some(connector_name) => {
|
||||
let merchant_connector_account = construct_connector_label_and_get_mca(
|
||||
state,
|
||||
merchant_account,
|
||||
payment_data,
|
||||
&connector_name,
|
||||
key_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let connector = api::ConnectorData::get_connector_by_name(
|
||||
&state.conf.connectors,
|
||||
&connector_name,
|
||||
@ -823,6 +913,7 @@ where
|
||||
merchant_account,
|
||||
key_store,
|
||||
customer,
|
||||
&merchant_connector_account,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -919,6 +1010,35 @@ pub fn is_preprocessing_required_for_wallets(connector_name: String) -> bool {
|
||||
connector_name == *"trustpay" || connector_name == *"payme"
|
||||
}
|
||||
|
||||
pub async fn construct_connector_label_and_get_mca<'a, F>(
|
||||
state: &'a AppState,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
payment_data: &mut PaymentData<F>,
|
||||
connector_id: &str,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<helpers::MerchantConnectorAccountType>
|
||||
where
|
||||
F: Clone,
|
||||
{
|
||||
let connector_label = helpers::get_connector_label(
|
||||
payment_data.payment_intent.business_country,
|
||||
&payment_data.payment_intent.business_label,
|
||||
payment_data.payment_attempt.business_sub_label.as_ref(),
|
||||
connector_id,
|
||||
);
|
||||
|
||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||
state,
|
||||
merchant_account.merchant_id.as_str(),
|
||||
&connector_label,
|
||||
payment_data.creds_identifier.to_owned(),
|
||||
key_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(merchant_connector_account)
|
||||
}
|
||||
|
||||
fn is_payment_method_tokenization_enabled_for_connector(
|
||||
state: &AppState,
|
||||
connector_name: &str,
|
||||
@ -941,6 +1061,47 @@ fn is_payment_method_tokenization_enabled_for_connector(
|
||||
.unwrap_or(false))
|
||||
}
|
||||
|
||||
fn is_apple_pay_predecrypt(
|
||||
payment_method_type: &Option<api_models::enums::PaymentMethodType>,
|
||||
merchant_connector_account: &Option<helpers::MerchantConnectorAccountType>,
|
||||
) -> RouterResult<bool> {
|
||||
Ok(payment_method_type
|
||||
.map(|pmt| match pmt {
|
||||
api_models::enums::PaymentMethodType::ApplePay => {
|
||||
check_apple_pay_metadata(merchant_connector_account)
|
||||
}
|
||||
_ => Ok(false),
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(false))
|
||||
}
|
||||
|
||||
fn check_apple_pay_metadata(
|
||||
merchant_connector_account: &Option<helpers::MerchantConnectorAccountType>,
|
||||
) -> RouterResult<bool> {
|
||||
let apple_pay_predecrypt = merchant_connector_account
|
||||
.clone()
|
||||
.and_then(|mca| {
|
||||
let metadata = mca.get_metadata();
|
||||
metadata.and_then(|apple_pay_metadata| {
|
||||
let parsed_metadata: Result<api_models::payments::ApplepaySessionTokenData, _> =
|
||||
apple_pay_metadata.parse_value("ApplepaySessionTokenData");
|
||||
|
||||
parsed_metadata.ok().map(|metadata| match metadata.data {
|
||||
api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined(
|
||||
apple_pay_combined,
|
||||
) => match apple_pay_combined {
|
||||
api_models::payments::ApplePayCombinedMetadata::Simplified { .. } => true,
|
||||
api_models::payments::ApplePayCombinedMetadata::Manual { .. } => false,
|
||||
},
|
||||
api_models::payments::ApplepaySessionTokenMetadata::ApplePay(_) => false,
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap_or(false);
|
||||
Ok(apple_pay_predecrypt)
|
||||
}
|
||||
|
||||
fn is_payment_method_type_allowed_for_connector(
|
||||
current_pm_type: &Option<storage::enums::PaymentMethodType>,
|
||||
pm_type_filter: Option<PaymentMethodTypeTokenFilter>,
|
||||
@ -961,11 +1122,16 @@ async fn decide_payment_method_tokenize_action(
|
||||
payment_method: &storage::enums::PaymentMethod,
|
||||
pm_parent_token: Option<&String>,
|
||||
is_connector_tokenization_enabled: bool,
|
||||
is_apple_pay_predecrypt_supported: bool,
|
||||
) -> RouterResult<TokenizationAction> {
|
||||
match pm_parent_token {
|
||||
None => {
|
||||
if is_connector_tokenization_enabled {
|
||||
if is_connector_tokenization_enabled && is_apple_pay_predecrypt_supported {
|
||||
Ok(TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt)
|
||||
} else if is_connector_tokenization_enabled {
|
||||
Ok(TokenizationAction::TokenizeInConnectorAndRouter)
|
||||
} else if is_apple_pay_predecrypt_supported {
|
||||
Ok(TokenizationAction::DecryptApplePayToken)
|
||||
} else {
|
||||
Ok(TokenizationAction::TokenizeInRouter)
|
||||
}
|
||||
@ -993,8 +1159,12 @@ async fn decide_payment_method_tokenize_action(
|
||||
match connector_token_option {
|
||||
Some(connector_token) => Ok(TokenizationAction::ConnectorToken(connector_token)),
|
||||
None => {
|
||||
if is_connector_tokenization_enabled {
|
||||
Ok(TokenizationAction::TokenizeInConnector)
|
||||
if is_connector_tokenization_enabled && is_apple_pay_predecrypt_supported {
|
||||
Ok(TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt)
|
||||
} else if is_connector_tokenization_enabled {
|
||||
Ok(TokenizationAction::TokenizeInConnectorAndRouter)
|
||||
} else if is_apple_pay_predecrypt_supported {
|
||||
Ok(TokenizationAction::DecryptApplePayToken)
|
||||
} else {
|
||||
Ok(TokenizationAction::TokenizeInRouter)
|
||||
}
|
||||
@ -1011,14 +1181,17 @@ pub enum TokenizationAction {
|
||||
TokenizeInConnectorAndRouter,
|
||||
ConnectorToken(String),
|
||||
SkipConnectorTokenization,
|
||||
DecryptApplePayToken,
|
||||
TokenizeInConnectorAndApplepayPreDecrypt,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn get_connector_tokenization_action<F, Req>(
|
||||
pub async fn get_connector_tokenization_action_when_confirm_true<F, Req>(
|
||||
state: &AppState,
|
||||
operation: &BoxedOperation<'_, F, Req>,
|
||||
mut payment_data: PaymentData<F>,
|
||||
payment_data: &mut PaymentData<F>,
|
||||
validate_result: &operations::ValidateResult<'_>,
|
||||
merchant_connector_account: &helpers::MerchantConnectorAccountType,
|
||||
) -> RouterResult<(PaymentData<F>, TokenizationAction)>
|
||||
where
|
||||
F: Send + Clone,
|
||||
@ -1036,13 +1209,17 @@ where
|
||||
.unwrap_or(false);
|
||||
|
||||
let payment_data_and_tokenization_action = match connector {
|
||||
Some(_) if is_mandate => (payment_data, TokenizationAction::SkipConnectorTokenization),
|
||||
Some(_) if is_mandate => (
|
||||
payment_data.to_owned(),
|
||||
TokenizationAction::SkipConnectorTokenization,
|
||||
),
|
||||
Some(connector) if is_operation_confirm(&operation) => {
|
||||
let payment_method = &payment_data
|
||||
.payment_attempt
|
||||
.payment_method
|
||||
.get_required_value("payment_method")?;
|
||||
let payment_method_type = &payment_data.payment_attempt.payment_method_type;
|
||||
|
||||
let is_connector_tokenization_enabled =
|
||||
is_payment_method_tokenization_enabled_for_connector(
|
||||
state,
|
||||
@ -1051,12 +1228,18 @@ where
|
||||
payment_method_type,
|
||||
)?;
|
||||
|
||||
let is_apple_pay_predecrypt = is_apple_pay_predecrypt(
|
||||
payment_method_type,
|
||||
&Some(merchant_connector_account.clone()),
|
||||
)?;
|
||||
|
||||
let payment_method_action = decide_payment_method_tokenize_action(
|
||||
state,
|
||||
&connector,
|
||||
payment_method,
|
||||
payment_data.token.as_ref(),
|
||||
is_connector_tokenization_enabled,
|
||||
is_apple_pay_predecrypt,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -1064,7 +1247,7 @@ where
|
||||
TokenizationAction::TokenizeInRouter => {
|
||||
let (_operation, payment_method_data) = operation
|
||||
.to_domain()?
|
||||
.make_pm_data(state, &mut payment_data, validate_result.storage_scheme)
|
||||
.make_pm_data(state, payment_data, validate_result.storage_scheme)
|
||||
.await?;
|
||||
payment_data.payment_method_data = payment_method_data;
|
||||
TokenizationAction::SkipConnectorTokenization
|
||||
@ -1074,7 +1257,7 @@ where
|
||||
TokenizationAction::TokenizeInConnectorAndRouter => {
|
||||
let (_operation, payment_method_data) = operation
|
||||
.to_domain()?
|
||||
.make_pm_data(state, &mut payment_data, validate_result.storage_scheme)
|
||||
.make_pm_data(state, payment_data, validate_result.storage_scheme)
|
||||
.await?;
|
||||
|
||||
payment_data.payment_method_data = payment_method_data;
|
||||
@ -1087,22 +1270,47 @@ where
|
||||
TokenizationAction::SkipConnectorTokenization => {
|
||||
TokenizationAction::SkipConnectorTokenization
|
||||
}
|
||||
TokenizationAction::DecryptApplePayToken => {
|
||||
TokenizationAction::DecryptApplePayToken
|
||||
}
|
||||
TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt => {
|
||||
TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt
|
||||
}
|
||||
};
|
||||
(payment_data, connector_tokenization_action)
|
||||
}
|
||||
_ => {
|
||||
let (_operation, payment_method_data) = operation
|
||||
.to_domain()?
|
||||
.make_pm_data(state, &mut payment_data, validate_result.storage_scheme)
|
||||
.await?;
|
||||
payment_data.payment_method_data = payment_method_data;
|
||||
(payment_data, TokenizationAction::SkipConnectorTokenization)
|
||||
(payment_data.to_owned(), connector_tokenization_action)
|
||||
}
|
||||
_ => (
|
||||
payment_data.to_owned(),
|
||||
TokenizationAction::SkipConnectorTokenization,
|
||||
),
|
||||
};
|
||||
|
||||
Ok(payment_data_and_tokenization_action)
|
||||
}
|
||||
|
||||
pub async fn tokenize_in_router_when_confirm_false<F, Req>(
|
||||
state: &AppState,
|
||||
operation: &BoxedOperation<'_, F, Req>,
|
||||
payment_data: &mut PaymentData<F>,
|
||||
validate_result: &operations::ValidateResult<'_>,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: Send + Clone,
|
||||
{
|
||||
// On confirm is false and only router related
|
||||
let payment_data = if !is_operation_confirm(operation) {
|
||||
let (_operation, payment_method_data) = operation
|
||||
.to_domain()?
|
||||
.make_pm_data(state, payment_data, validate_result.storage_scheme)
|
||||
.await?;
|
||||
payment_data.payment_method_data = payment_method_data;
|
||||
payment_data
|
||||
} else {
|
||||
payment_data
|
||||
};
|
||||
Ok(payment_data.to_owned())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum CallConnectorAction {
|
||||
Trigger,
|
||||
|
||||
Reference in New Issue
Block a user