Skip to content

Commit efeebc0

Browse files
vspeckyChethan-rao
andauthored
fix(router): associate parent payment token with payment_method_id as hyperswitch token for saved cards (#2130)
Co-authored-by: Chethan Rao <[email protected]>
1 parent 644709d commit efeebc0

File tree

6 files changed

+320
-102
lines changed

6 files changed

+320
-102
lines changed

crates/router/src/core/errors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use storage_impl::errors as storage_impl_errors;
1919
pub use user::*;
2020

2121
pub use self::{
22-
api_error_response::ApiErrorResponse,
22+
api_error_response::{ApiErrorResponse, NotImplementedMessage},
2323
customers_error_response::CustomersErrorResponse,
2424
sch_errors::*,
2525
storage_errors::*,

crates/router/src/core/payment_methods.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ pub use api_models::{
99
pub use common_utils::request::RequestBody;
1010
use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent};
1111
use diesel_models::enums;
12+
use error_stack::IntoReport;
1213

1314
use crate::{
14-
core::{errors::RouterResult, payments::helpers},
15+
core::{
16+
errors::{self, RouterResult},
17+
payments::helpers,
18+
},
1519
routes::AppState,
1620
types::{
1721
api::{self, payments},
18-
domain,
22+
domain, storage,
1923
},
2024
};
2125

@@ -30,6 +34,14 @@ pub trait PaymentMethodRetrieve {
3034
payment_attempt: &PaymentAttempt,
3135
merchant_key_store: &domain::MerchantKeyStore,
3236
) -> RouterResult<(Option<payments::PaymentMethodData>, Option<String>)>;
37+
38+
async fn retrieve_payment_method_with_token(
39+
state: &AppState,
40+
key_store: &domain::MerchantKeyStore,
41+
token: &storage::PaymentTokenData,
42+
payment_intent: &PaymentIntent,
43+
card_cvc: Option<masking::Secret<String>>,
44+
) -> RouterResult<Option<(payments::PaymentMethodData, enums::PaymentMethod)>>;
3345
}
3446

3547
#[async_trait::async_trait]
@@ -105,4 +117,65 @@ impl PaymentMethodRetrieve for Oss {
105117
_ => Ok((None, None)),
106118
}
107119
}
120+
121+
async fn retrieve_payment_method_with_token(
122+
state: &AppState,
123+
merchant_key_store: &domain::MerchantKeyStore,
124+
token_data: &storage::PaymentTokenData,
125+
payment_intent: &PaymentIntent,
126+
card_cvc: Option<masking::Secret<String>>,
127+
) -> RouterResult<Option<(payments::PaymentMethodData, enums::PaymentMethod)>> {
128+
match token_data {
129+
storage::PaymentTokenData::TemporaryGeneric(generic_token) => {
130+
helpers::retrieve_payment_method_with_temporary_token(
131+
state,
132+
&generic_token.token,
133+
payment_intent,
134+
card_cvc,
135+
merchant_key_store,
136+
)
137+
.await
138+
}
139+
140+
storage::PaymentTokenData::Temporary(generic_token) => {
141+
helpers::retrieve_payment_method_with_temporary_token(
142+
state,
143+
&generic_token.token,
144+
payment_intent,
145+
card_cvc,
146+
merchant_key_store,
147+
)
148+
.await
149+
}
150+
151+
storage::PaymentTokenData::Permanent(card_token) => {
152+
helpers::retrieve_card_with_permanent_token(
153+
state,
154+
&card_token.token,
155+
payment_intent,
156+
card_cvc,
157+
)
158+
.await
159+
.map(|card| Some((card, enums::PaymentMethod::Card)))
160+
}
161+
162+
storage::PaymentTokenData::PermanentCard(card_token) => {
163+
helpers::retrieve_card_with_permanent_token(
164+
state,
165+
&card_token.token,
166+
payment_intent,
167+
card_cvc,
168+
)
169+
.await
170+
.map(|card| Some((card, enums::PaymentMethod::Card)))
171+
}
172+
173+
storage::PaymentTokenData::AuthBankDebit(_) => {
174+
Err(errors::ApiErrorResponse::NotImplemented {
175+
message: errors::NotImplementedMessage::Default,
176+
})
177+
.into_report()
178+
}
179+
}
180+
}
108181
}

crates/router/src/core/payment_methods/cards.rs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use crate::{
5050
self,
5151
types::{decrypt, encrypt_optional, AsyncLift},
5252
},
53-
storage::{self, enums},
53+
storage::{self, enums, PaymentTokenData},
5454
transformers::ForeignFrom,
5555
},
5656
utils::{self, ConnectorResponseExt, OptionExt},
@@ -2103,23 +2103,32 @@ pub async fn list_customer_payment_method(
21032103
let mut customer_pms = Vec::new();
21042104
for pm in resp.into_iter() {
21052105
let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token");
2106-
let hyperswitch_token = generate_id(consts::ID_LENGTH, "token");
21072106

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

2114-
#[cfg(feature = "payouts")]
2115-
let pmd = if pm.payment_method == enums::PaymentMethod::BankTransfer {
2116-
Some(
2117-
get_lookup_key_for_payout_method(state, &key_store, &hyperswitch_token, &pm)
2118-
.await?,
2119-
)
2120-
} else {
2121-
None
2114+
#[cfg(feature = "payouts")]
2115+
enums::PaymentMethod::BankTransfer => {
2116+
let token = generate_id(consts::ID_LENGTH, "token");
2117+
let token_data = PaymentTokenData::temporary_generic(token.clone());
2118+
(
2119+
None,
2120+
Some(get_lookup_key_for_payout_method(state, &key_store, &token, &pm).await?),
2121+
token_data,
2122+
)
2123+
}
2124+
2125+
_ => (
2126+
None,
2127+
None,
2128+
PaymentTokenData::temporary_generic(generate_id(consts::ID_LENGTH, "token")),
2129+
),
21222130
};
2131+
21232132
//Need validation for enabled payment method ,querying MCA
21242133
let pma = api::CustomerPaymentMethod {
21252134
payment_token: parent_payment_method_token.to_owned(),
@@ -2134,10 +2143,7 @@ pub async fn list_customer_payment_method(
21342143
installment_payment_enabled: false,
21352144
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
21362145
created: Some(pm.created_at),
2137-
#[cfg(feature = "payouts")]
21382146
bank_transfer: pmd,
2139-
#[cfg(not(feature = "payouts"))]
2140-
bank_transfer: None,
21412147
requires_cvv,
21422148
};
21432149
customer_pms.push(pma.to_owned());
@@ -2153,7 +2159,7 @@ pub async fn list_customer_payment_method(
21532159
&parent_payment_method_token,
21542160
pma.payment_method,
21552161
))
2156-
.insert(intent_created, hyperswitch_token, state)
2162+
.insert(intent_created, hyperswitch_token_data, state)
21572163
.await?;
21582164

21592165
if let Some(metadata) = pma.metadata {
@@ -2200,10 +2206,8 @@ async fn get_card_details(
22002206
pm: &payment_method::PaymentMethod,
22012207
key: &[u8],
22022208
state: &routes::AppState,
2203-
hyperswitch_token: &str,
2204-
key_store: &domain::MerchantKeyStore,
2205-
) -> errors::RouterResult<Option<api::CardDetailFromLocker>> {
2206-
let mut _card_decrypted =
2209+
) -> errors::RouterResult<api::CardDetailFromLocker> {
2210+
let card_decrypted =
22072211
decrypt::<serde_json::Value, masking::WithType>(pm.payment_method_data.clone(), key)
22082212
.await
22092213
.change_context(errors::StorageError::DecryptionError)
@@ -2217,16 +2221,17 @@ async fn get_card_details(
22172221
_ => None,
22182222
});
22192223

2220-
Ok(Some(
2221-
get_lookup_key_from_locker(state, hyperswitch_token, pm, key_store).await?,
2222-
))
2224+
Ok(if let Some(mut crd) = card_decrypted {
2225+
crd.scheme = pm.scheme.clone();
2226+
crd
2227+
} else {
2228+
get_card_details_from_locker(state, pm).await?
2229+
})
22232230
}
22242231

2225-
pub async fn get_lookup_key_from_locker(
2232+
pub async fn get_card_details_from_locker(
22262233
state: &routes::AppState,
2227-
payment_token: &str,
22282234
pm: &storage::PaymentMethod,
2229-
merchant_key_store: &domain::MerchantKeyStore,
22302235
) -> errors::RouterResult<api::CardDetailFromLocker> {
22312236
let card = get_card_from_locker(
22322237
state,
@@ -2237,9 +2242,19 @@ pub async fn get_lookup_key_from_locker(
22372242
.await
22382243
.change_context(errors::ApiErrorResponse::InternalServerError)
22392244
.attach_printable("Error getting card from card vault")?;
2240-
let card_detail = payment_methods::get_card_detail(pm, card)
2245+
2246+
payment_methods::get_card_detail(pm, card)
22412247
.change_context(errors::ApiErrorResponse::InternalServerError)
2242-
.attach_printable("Get Card Details Failed")?;
2248+
.attach_printable("Get Card Details Failed")
2249+
}
2250+
2251+
pub async fn get_lookup_key_from_locker(
2252+
state: &routes::AppState,
2253+
payment_token: &str,
2254+
pm: &storage::PaymentMethod,
2255+
merchant_key_store: &domain::MerchantKeyStore,
2256+
) -> errors::RouterResult<api::CardDetailFromLocker> {
2257+
let card_detail = get_card_details_from_locker(state, pm).await?;
22432258
let card = card_detail.clone();
22442259

22452260
let resp = TempLockerCardSupport::create_payment_method_data_in_temp_locker(

0 commit comments

Comments
 (0)