Skip to content

Commit

Permalink
fix(router): associate parent payment token with payment_method_id
Browse files Browse the repository at this point in the history
…as hyperswitch token for saved cards (#2130)

Co-authored-by: Chethan Rao <[email protected]>
  • Loading branch information
vspecky and Chethan-rao authored Nov 20, 2023
1 parent 644709d commit efeebc0
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 102 deletions.
2 changes: 1 addition & 1 deletion crates/router/src/core/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use storage_impl::errors as storage_impl_errors;
pub use user::*;

pub use self::{
api_error_response::ApiErrorResponse,
api_error_response::{ApiErrorResponse, NotImplementedMessage},
customers_error_response::CustomersErrorResponse,
sch_errors::*,
storage_errors::*,
Expand Down
77 changes: 75 additions & 2 deletions crates/router/src/core/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ pub use api_models::{
pub use common_utils::request::RequestBody;
use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent};
use diesel_models::enums;
use error_stack::IntoReport;

use crate::{
core::{errors::RouterResult, payments::helpers},
core::{
errors::{self, RouterResult},
payments::helpers,
},
routes::AppState,
types::{
api::{self, payments},
domain,
domain, storage,
},
};

Expand All @@ -30,6 +34,14 @@ pub trait PaymentMethodRetrieve {
payment_attempt: &PaymentAttempt,
merchant_key_store: &domain::MerchantKeyStore,
) -> RouterResult<(Option<payments::PaymentMethodData>, Option<String>)>;

async fn retrieve_payment_method_with_token(
state: &AppState,
key_store: &domain::MerchantKeyStore,
token: &storage::PaymentTokenData,
payment_intent: &PaymentIntent,
card_cvc: Option<masking::Secret<String>>,
) -> RouterResult<Option<(payments::PaymentMethodData, enums::PaymentMethod)>>;
}

#[async_trait::async_trait]
Expand Down Expand Up @@ -105,4 +117,65 @@ impl PaymentMethodRetrieve for Oss {
_ => Ok((None, None)),
}
}

async fn retrieve_payment_method_with_token(
state: &AppState,
merchant_key_store: &domain::MerchantKeyStore,
token_data: &storage::PaymentTokenData,
payment_intent: &PaymentIntent,
card_cvc: Option<masking::Secret<String>>,
) -> RouterResult<Option<(payments::PaymentMethodData, enums::PaymentMethod)>> {
match token_data {
storage::PaymentTokenData::TemporaryGeneric(generic_token) => {
helpers::retrieve_payment_method_with_temporary_token(
state,
&generic_token.token,
payment_intent,
card_cvc,
merchant_key_store,
)
.await
}

storage::PaymentTokenData::Temporary(generic_token) => {
helpers::retrieve_payment_method_with_temporary_token(
state,
&generic_token.token,
payment_intent,
card_cvc,
merchant_key_store,
)
.await
}

storage::PaymentTokenData::Permanent(card_token) => {
helpers::retrieve_card_with_permanent_token(
state,
&card_token.token,
payment_intent,
card_cvc,
)
.await
.map(|card| Some((card, enums::PaymentMethod::Card)))
}

storage::PaymentTokenData::PermanentCard(card_token) => {
helpers::retrieve_card_with_permanent_token(
state,
&card_token.token,
payment_intent,
card_cvc,
)
.await
.map(|card| Some((card, enums::PaymentMethod::Card)))
}

storage::PaymentTokenData::AuthBankDebit(_) => {
Err(errors::ApiErrorResponse::NotImplemented {
message: errors::NotImplementedMessage::Default,
})
.into_report()
}
}
}
}
77 changes: 46 additions & 31 deletions crates/router/src/core/payment_methods/cards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use crate::{
self,
types::{decrypt, encrypt_optional, AsyncLift},
},
storage::{self, enums},
storage::{self, enums, PaymentTokenData},
transformers::ForeignFrom,
},
utils::{self, ConnectorResponseExt, OptionExt},
Expand Down Expand Up @@ -2103,23 +2103,32 @@ pub async fn list_customer_payment_method(
let mut customer_pms = Vec::new();
for pm in resp.into_iter() {
let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token");
let hyperswitch_token = generate_id(consts::ID_LENGTH, "token");

let card = if pm.payment_method == enums::PaymentMethod::Card {
get_card_details(&pm, key, state, &hyperswitch_token, &key_store).await?
} else {
None
};
let (card, pmd, hyperswitch_token_data) = match pm.payment_method {
enums::PaymentMethod::Card => (
Some(get_card_details(&pm, key, state).await?),
None,
PaymentTokenData::permanent_card(pm.payment_method_id.clone()),
),

#[cfg(feature = "payouts")]
let pmd = if pm.payment_method == enums::PaymentMethod::BankTransfer {
Some(
get_lookup_key_for_payout_method(state, &key_store, &hyperswitch_token, &pm)
.await?,
)
} else {
None
#[cfg(feature = "payouts")]
enums::PaymentMethod::BankTransfer => {
let token = generate_id(consts::ID_LENGTH, "token");
let token_data = PaymentTokenData::temporary_generic(token.clone());
(
None,
Some(get_lookup_key_for_payout_method(state, &key_store, &token, &pm).await?),
token_data,
)
}

_ => (
None,
None,
PaymentTokenData::temporary_generic(generate_id(consts::ID_LENGTH, "token")),
),
};

//Need validation for enabled payment method ,querying MCA
let pma = api::CustomerPaymentMethod {
payment_token: parent_payment_method_token.to_owned(),
Expand All @@ -2134,10 +2143,7 @@ pub async fn list_customer_payment_method(
installment_payment_enabled: false,
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
created: Some(pm.created_at),
#[cfg(feature = "payouts")]
bank_transfer: pmd,
#[cfg(not(feature = "payouts"))]
bank_transfer: None,
requires_cvv,
};
customer_pms.push(pma.to_owned());
Expand All @@ -2153,7 +2159,7 @@ pub async fn list_customer_payment_method(
&parent_payment_method_token,
pma.payment_method,
))
.insert(intent_created, hyperswitch_token, state)
.insert(intent_created, hyperswitch_token_data, state)
.await?;

if let Some(metadata) = pma.metadata {
Expand Down Expand Up @@ -2200,10 +2206,8 @@ async fn get_card_details(
pm: &payment_method::PaymentMethod,
key: &[u8],
state: &routes::AppState,
hyperswitch_token: &str,
key_store: &domain::MerchantKeyStore,
) -> errors::RouterResult<Option<api::CardDetailFromLocker>> {
let mut _card_decrypted =
) -> errors::RouterResult<api::CardDetailFromLocker> {
let card_decrypted =
decrypt::<serde_json::Value, masking::WithType>(pm.payment_method_data.clone(), key)
.await
.change_context(errors::StorageError::DecryptionError)
Expand All @@ -2217,16 +2221,17 @@ async fn get_card_details(
_ => None,
});

Ok(Some(
get_lookup_key_from_locker(state, hyperswitch_token, pm, key_store).await?,
))
Ok(if let Some(mut crd) = card_decrypted {
crd.scheme = pm.scheme.clone();
crd
} else {
get_card_details_from_locker(state, pm).await?
})
}

pub async fn get_lookup_key_from_locker(
pub async fn get_card_details_from_locker(
state: &routes::AppState,
payment_token: &str,
pm: &storage::PaymentMethod,
merchant_key_store: &domain::MerchantKeyStore,
) -> errors::RouterResult<api::CardDetailFromLocker> {
let card = get_card_from_locker(
state,
Expand All @@ -2237,9 +2242,19 @@ pub async fn get_lookup_key_from_locker(
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting card from card vault")?;
let card_detail = payment_methods::get_card_detail(pm, card)

payment_methods::get_card_detail(pm, card)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Get Card Details Failed")?;
.attach_printable("Get Card Details Failed")
}

pub async fn get_lookup_key_from_locker(
state: &routes::AppState,
payment_token: &str,
pm: &storage::PaymentMethod,
merchant_key_store: &domain::MerchantKeyStore,
) -> errors::RouterResult<api::CardDetailFromLocker> {
let card_detail = get_card_details_from_locker(state, pm).await?;
let card = card_detail.clone();

let resp = TempLockerCardSupport::create_payment_method_data_in_temp_locker(
Expand Down
Loading

0 comments on commit efeebc0

Please sign in to comment.