Skip to content

Commit

Permalink
feat(payment_methods): enable auto-retries for apple pay (#4721)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
ShankarSinghC and hyperswitch-bot[bot] authored May 22, 2024
1 parent 8afeda5 commit d942a31
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 31 deletions.
22 changes: 22 additions & 0 deletions crates/router/src/core/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3170,6 +3170,28 @@ where
{
routing_data.business_sub_label = choice.sub_label.clone();
}

if payment_data.payment_attempt.payment_method_type
== Some(storage_enums::PaymentMethodType::ApplePay)
{
let retryable_connector_data = helpers::get_apple_pay_retryable_connectors(
state,
merchant_account,
payment_data,
key_store,
connector_data.clone(),
#[cfg(feature = "connector_choice_mca_id")]
choice.merchant_connector_id.clone().as_ref(),
#[cfg(not(feature = "connector_choice_mca_id"))]
None,
)
.await?;

if let Some(connector_data_list) = retryable_connector_data {
return Ok(ConnectorCallType::Retryable(connector_data_list));
}
}

return Ok(ConnectorCallType::PreDetermined(connector_data));
}
}
Expand Down
39 changes: 8 additions & 31 deletions crates/router/src/core/payments/flows/session_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,36 +122,6 @@ fn is_dynamic_fields_required(
.unwrap_or(false)
}

fn get_applepay_metadata(
connector_metadata: Option<common_utils::pii::SecretSerdeValue>,
) -> RouterResult<payment_types::ApplepaySessionTokenMetadata> {
connector_metadata
.clone()
.parse_value::<api_models::payments::ApplepayCombinedSessionTokenData>(
"ApplepayCombinedSessionTokenData",
)
.map(|combined_metadata| {
api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined(
combined_metadata.apple_pay_combined,
)
})
.or_else(|_| {
connector_metadata
.parse_value::<api_models::payments::ApplepaySessionTokenData>(
"ApplepaySessionTokenData",
)
.map(|old_metadata| {
api_models::payments::ApplepaySessionTokenMetadata::ApplePay(
old_metadata.apple_pay,
)
})
})
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
field_name: "connector_metadata".to_string(),
expected_format: "applepay_metadata_format".to_string(),
})
}

fn build_apple_pay_session_request(
state: &routes::AppState,
request: payment_types::ApplepaySessionRequest,
Expand Down Expand Up @@ -196,7 +166,8 @@ async fn create_applepay_session_token(
)
} else {
// Get the apple pay metadata
let apple_pay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?;
let apple_pay_metadata =
helpers::get_applepay_metadata(router_data.connector_meta_data.clone())?;

// Get payment request data , apple pay session request and merchant keys
let (
Expand All @@ -213,6 +184,8 @@ async fn create_applepay_session_token(
payment_request_data,
session_token_data,
} => {
logger::info!("Apple pay simplified flow");

let merchant_identifier = state
.conf
.applepay_merchant_configs
Expand Down Expand Up @@ -254,6 +227,8 @@ async fn create_applepay_session_token(
payment_request_data,
session_token_data,
} => {
logger::info!("Apple pay manual flow");

let apple_pay_session_request =
get_session_request_for_manual_apple_pay(session_token_data.clone());

Expand All @@ -269,6 +244,8 @@ async fn create_applepay_session_token(
}
},
payment_types::ApplepaySessionTokenMetadata::ApplePay(apple_pay_metadata) => {
logger::info!("Apple pay manual flow");

let apple_pay_session_request = get_session_request_for_manual_apple_pay(
apple_pay_metadata.session_token_data.clone(),
);
Expand Down
118 changes: 118 additions & 0 deletions crates/router/src/core/payments/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3888,6 +3888,122 @@ pub fn validate_customer_access(
Ok(())
}

pub fn is_apple_pay_simplified_flow(
connector_metadata: Option<pii::SecretSerdeValue>,
) -> CustomResult<bool, errors::ApiErrorResponse> {
let apple_pay_metadata = get_applepay_metadata(connector_metadata)?;

Ok(match apple_pay_metadata {
api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined(
apple_pay_combined_metadata,
) => match apple_pay_combined_metadata {
api_models::payments::ApplePayCombinedMetadata::Simplified { .. } => true,
api_models::payments::ApplePayCombinedMetadata::Manual { .. } => false,
},
api_models::payments::ApplepaySessionTokenMetadata::ApplePay(_) => false,
})
}

pub fn get_applepay_metadata(
connector_metadata: Option<pii::SecretSerdeValue>,
) -> RouterResult<api_models::payments::ApplepaySessionTokenMetadata> {
connector_metadata
.clone()
.parse_value::<api_models::payments::ApplepayCombinedSessionTokenData>(
"ApplepayCombinedSessionTokenData",
)
.map(|combined_metadata| {
api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined(
combined_metadata.apple_pay_combined,
)
})
.or_else(|_| {
connector_metadata
.parse_value::<api_models::payments::ApplepaySessionTokenData>(
"ApplepaySessionTokenData",
)
.map(|old_metadata| {
api_models::payments::ApplepaySessionTokenMetadata::ApplePay(
old_metadata.apple_pay,
)
})
})
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
field_name: "connector_metadata".to_string(),
expected_format: "applepay_metadata_format".to_string(),
})
}

pub async fn get_apple_pay_retryable_connectors<F>(
state: AppState,
merchant_account: &domain::MerchantAccount,
payment_data: &mut PaymentData<F>,
key_store: &domain::MerchantKeyStore,
decided_connector_data: api::ConnectorData,
merchant_connector_id: Option<&String>,
) -> CustomResult<Option<Vec<api::ConnectorData>>, errors::ApiErrorResponse>
where
F: Send + Clone,
{
let profile_id = &payment_data
.payment_intent
.profile_id
.clone()
.get_required_value("profile_id")
.change_context(errors::ApiErrorResponse::MissingRequiredField {
field_name: "profile_id",
})?;

let merchant_connector_account = get_merchant_connector_account(
&state,
merchant_account.merchant_id.as_str(),
payment_data.creds_identifier.to_owned(),
key_store,
profile_id, // need to fix this
&decided_connector_data.connector_name.to_string(),
merchant_connector_id,
)
.await?
.get_metadata();

let connector_data_list = if is_apple_pay_simplified_flow(merchant_connector_account)? {
let merchant_connector_account_list = state
.store
.find_merchant_connector_account_by_merchant_id_and_disabled_list(
merchant_account.merchant_id.as_str(),
true,
key_store,
)
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;

let mut connector_data_list = vec![decided_connector_data.clone()];

for merchant_connector_account in merchant_connector_account_list {
if is_apple_pay_simplified_flow(merchant_connector_account.metadata)? {
let connector_data = api::ConnectorData::get_connector_by_name(
&state.conf.connectors,
&merchant_connector_account.connector_name.to_string(),
api::GetToken::Connector,
Some(merchant_connector_account.merchant_connector_id),
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Invalid connector name received")?;

if !connector_data_list.iter().any(|connector_details| {
connector_details.merchant_connector_id == connector_data.merchant_connector_id
}) {
connector_data_list.push(connector_data)
}
}
}
Some(connector_data_list)
} else {
None
};
Ok(connector_data_list)
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ApplePayData {
version: masking::Secret<String>,
Expand Down Expand Up @@ -4040,6 +4156,8 @@ impl ApplePayData {
&self,
symmetric_key: &[u8],
) -> CustomResult<String, errors::ApplePayDecryptionError> {
logger::info!("Decrypt apple pay token");

let data = BASE64_ENGINE
.decode(self.data.peek().as_bytes())
.change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?;
Expand Down

0 comments on commit d942a31

Please sign in to comment.