mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
fix(router): associate parent payment token with payment_method_id as hyperswitch token for saved cards (#2130)
Co-authored-by: Chethan Rao <70657455+Chethan-rao@users.noreply.github.com>
This commit is contained in:
@ -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::*,
|
||||
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@ -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]
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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},
|
||||
@ -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")]
|
||||
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")),
|
||||
),
|
||||
};
|
||||
|
||||
#[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
|
||||
};
|
||||
//Need validation for enabled payment method ,querying MCA
|
||||
let pma = api::CustomerPaymentMethod {
|
||||
payment_token: parent_payment_method_token.to_owned(),
|
||||
@ -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());
|
||||
@ -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 {
|
||||
@ -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)
|
||||
@ -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,
|
||||
@ -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(
|
||||
|
||||
@ -55,7 +55,7 @@ use crate::{
|
||||
utils::{
|
||||
self,
|
||||
crypto::{self, SignMessage},
|
||||
OptionExt,
|
||||
OptionExt, StringExt,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1326,6 +1326,114 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, Ctx>(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn retrieve_payment_method_with_temporary_token(
|
||||
state: &AppState,
|
||||
token: &str,
|
||||
payment_intent: &PaymentIntent,
|
||||
card_cvc: Option<masking::Secret<String>>,
|
||||
merchant_key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Option<(api::PaymentMethodData, enums::PaymentMethod)>> {
|
||||
let (pm, supplementary_data) =
|
||||
vault::Vault::get_payment_method_data_from_locker(state, token, merchant_key_store)
|
||||
.await
|
||||
.attach_printable(
|
||||
"Payment method for given token not found or there was a problem fetching it",
|
||||
)?;
|
||||
|
||||
utils::when(
|
||||
supplementary_data
|
||||
.customer_id
|
||||
.ne(&payment_intent.customer_id),
|
||||
|| {
|
||||
Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payment method and customer passed in payment are not same".into() })
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok::<_, error_stack::Report<errors::ApiErrorResponse>>(match pm {
|
||||
Some(api::PaymentMethodData::Card(card)) => {
|
||||
if let Some(cvc) = card_cvc {
|
||||
let mut updated_card = card;
|
||||
updated_card.card_cvc = cvc;
|
||||
let updated_pm = api::PaymentMethodData::Card(updated_card);
|
||||
vault::Vault::store_payment_method_data_in_locker(
|
||||
state,
|
||||
Some(token.to_owned()),
|
||||
&updated_pm,
|
||||
payment_intent.customer_id.to_owned(),
|
||||
enums::PaymentMethod::Card,
|
||||
merchant_key_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Some((updated_pm, enums::PaymentMethod::Card))
|
||||
} else {
|
||||
Some((
|
||||
api::PaymentMethodData::Card(card),
|
||||
enums::PaymentMethod::Card,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Some(the_pm @ api::PaymentMethodData::Wallet(_)) => {
|
||||
Some((the_pm, enums::PaymentMethod::Wallet))
|
||||
}
|
||||
|
||||
Some(the_pm @ api::PaymentMethodData::BankTransfer(_)) => {
|
||||
Some((the_pm, enums::PaymentMethod::BankTransfer))
|
||||
}
|
||||
|
||||
Some(the_pm @ api::PaymentMethodData::BankRedirect(_)) => {
|
||||
Some((the_pm, enums::PaymentMethod::BankRedirect))
|
||||
}
|
||||
|
||||
Some(_) => Err(errors::ApiErrorResponse::InternalServerError)
|
||||
.into_report()
|
||||
.attach_printable("Payment method received from locker is unsupported by locker")?,
|
||||
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn retrieve_card_with_permanent_token(
|
||||
state: &AppState,
|
||||
token: &str,
|
||||
payment_intent: &PaymentIntent,
|
||||
card_cvc: Option<masking::Secret<String>>,
|
||||
) -> RouterResult<api::PaymentMethodData> {
|
||||
let customer_id = payment_intent
|
||||
.customer_id
|
||||
.as_ref()
|
||||
.get_required_value("customer_id")
|
||||
.change_context(errors::ApiErrorResponse::UnprocessableEntity {
|
||||
message: "no customer id provided for the payment".to_string(),
|
||||
})?;
|
||||
|
||||
let card = cards::get_card_from_locker(state, customer_id, &payment_intent.merchant_id, token)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("failed to fetch card information from the permanent locker")?;
|
||||
|
||||
let api_card = api::Card {
|
||||
card_number: card.card_number,
|
||||
card_holder_name: card
|
||||
.name_on_card
|
||||
.get_required_value("name_on_card")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("card holder name was not saved in permanent locker")?,
|
||||
card_exp_month: card.card_exp_month,
|
||||
card_exp_year: card.card_exp_year,
|
||||
card_cvc: card_cvc.unwrap_or_default(),
|
||||
card_issuer: card.card_brand,
|
||||
nick_name: card.nick_name.map(masking::Secret::new),
|
||||
card_network: None,
|
||||
card_type: None,
|
||||
card_issuing_country: None,
|
||||
bank_code: None,
|
||||
};
|
||||
|
||||
Ok(api::PaymentMethodData::Card(api_card))
|
||||
}
|
||||
|
||||
pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>(
|
||||
operation: BoxedOperation<'a, F, R, Ctx>,
|
||||
state: &'a AppState,
|
||||
@ -1339,7 +1447,7 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>(
|
||||
let token = payment_data.token.clone();
|
||||
|
||||
let hyperswitch_token = match payment_data.mandate_id {
|
||||
Some(_) => token,
|
||||
Some(_) => token.map(storage::PaymentTokenData::temporary_generic),
|
||||
None => {
|
||||
if let Some(token) = token {
|
||||
let redis_conn = state
|
||||
@ -1358,7 +1466,7 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>(
|
||||
.get_required_value("payment_method")?,
|
||||
);
|
||||
|
||||
let key = redis_conn
|
||||
let token_data_string = redis_conn
|
||||
.get_key::<Option<String>>(&key)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
@ -1369,7 +1477,26 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>(
|
||||
},
|
||||
))?;
|
||||
|
||||
Some(key)
|
||||
let token_data_result = token_data_string
|
||||
.clone()
|
||||
.parse_struct("PaymentTokenData")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("failed to deserialize hyperswitch token data");
|
||||
|
||||
let token_data = match token_data_result {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
// The purpose of this logic is backwards compatibility to support tokens
|
||||
// in redis that might be following the old format.
|
||||
if token_data_string.starts_with('{') {
|
||||
return Err(e);
|
||||
} else {
|
||||
storage::PaymentTokenData::temporary_generic(token_data_string)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Some(token_data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -1381,72 +1508,24 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>(
|
||||
// TODO: Handle case where payment method and token both are present in request properly.
|
||||
let payment_method = match (request, hyperswitch_token) {
|
||||
(_, Some(hyperswitch_token)) => {
|
||||
let (pm, supplementary_data) = vault::Vault::get_payment_method_data_from_locker(
|
||||
let payment_method_details = Ctx::retrieve_payment_method_with_token(
|
||||
state,
|
||||
&hyperswitch_token,
|
||||
merchant_key_store,
|
||||
&hyperswitch_token,
|
||||
&payment_data.payment_intent,
|
||||
card_cvc,
|
||||
)
|
||||
.await
|
||||
.attach_printable(
|
||||
"Payment method for given token not found or there was a problem fetching it",
|
||||
)?;
|
||||
.attach_printable("in 'make_pm_data'")?;
|
||||
|
||||
utils::when(
|
||||
supplementary_data
|
||||
.customer_id
|
||||
.ne(&payment_data.payment_intent.customer_id),
|
||||
|| {
|
||||
Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payment method and customer passed in payment are not same".into() })
|
||||
Ok::<_, error_stack::Report<errors::ApiErrorResponse>>(
|
||||
if let Some((payment_method_data, payment_method)) = payment_method_details {
|
||||
payment_data.payment_attempt.payment_method = Some(payment_method);
|
||||
Some(payment_method_data)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok::<_, error_stack::Report<errors::ApiErrorResponse>>(match pm.clone() {
|
||||
Some(api::PaymentMethodData::Card(card)) => {
|
||||
payment_data.payment_attempt.payment_method =
|
||||
Some(storage_enums::PaymentMethod::Card);
|
||||
if let Some(cvc) = card_cvc {
|
||||
let mut updated_card = card;
|
||||
updated_card.card_cvc = cvc;
|
||||
let updated_pm = api::PaymentMethodData::Card(updated_card);
|
||||
vault::Vault::store_payment_method_data_in_locker(
|
||||
state,
|
||||
Some(hyperswitch_token),
|
||||
&updated_pm,
|
||||
payment_data.payment_intent.customer_id.to_owned(),
|
||||
enums::PaymentMethod::Card,
|
||||
merchant_key_store,
|
||||
)
|
||||
.await?;
|
||||
Some(updated_pm)
|
||||
} else {
|
||||
pm
|
||||
}
|
||||
}
|
||||
|
||||
Some(api::PaymentMethodData::Wallet(_)) => {
|
||||
payment_data.payment_attempt.payment_method =
|
||||
Some(storage_enums::PaymentMethod::Wallet);
|
||||
pm
|
||||
}
|
||||
|
||||
Some(api::PaymentMethodData::BankTransfer(_)) => {
|
||||
payment_data.payment_attempt.payment_method =
|
||||
Some(storage_enums::PaymentMethod::BankTransfer);
|
||||
pm
|
||||
}
|
||||
Some(api::PaymentMethodData::BankRedirect(_)) => {
|
||||
payment_data.payment_attempt.payment_method =
|
||||
Some(storage_enums::PaymentMethod::BankRedirect);
|
||||
pm
|
||||
}
|
||||
Some(_) => Err(errors::ApiErrorResponse::InternalServerError)
|
||||
.into_report()
|
||||
.attach_printable(
|
||||
"Payment method received from locker is unsupported by locker",
|
||||
)?,
|
||||
|
||||
None => None,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
(Some(_), _) => {
|
||||
@ -1495,7 +1574,11 @@ pub async fn store_in_vault_and_generate_ppmt(
|
||||
});
|
||||
if let Some(key_for_hyperswitch_token) = key_for_hyperswitch_token {
|
||||
key_for_hyperswitch_token
|
||||
.insert(Some(payment_intent.created_at), router_token, state)
|
||||
.insert(
|
||||
Some(payment_intent.created_at),
|
||||
storage::PaymentTokenData::temporary_generic(router_token),
|
||||
state,
|
||||
)
|
||||
.await?;
|
||||
};
|
||||
Ok(parent_payment_method_token)
|
||||
|
||||
@ -9,7 +9,11 @@ use super::app::AppState;
|
||||
use crate::{
|
||||
core::{api_locking, errors, payment_methods::cards},
|
||||
services::{api, authentication as auth},
|
||||
types::api::payment_methods::{self, PaymentMethodId},
|
||||
types::{
|
||||
api::payment_methods::{self, PaymentMethodId},
|
||||
storage::payment_method::PaymentTokenData,
|
||||
},
|
||||
utils::Encode,
|
||||
};
|
||||
|
||||
/// PaymentMethods - Create
|
||||
@ -379,9 +383,12 @@ impl ParentPaymentMethodToken {
|
||||
pub async fn insert(
|
||||
&self,
|
||||
intent_created_at: Option<PrimitiveDateTime>,
|
||||
token: String,
|
||||
token: PaymentTokenData,
|
||||
state: &AppState,
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
let token_json_str = Encode::<PaymentTokenData>::encode_to_string_of_json(&token)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("failed to serialize hyperswitch token to json")?;
|
||||
let redis_conn = state
|
||||
.store
|
||||
.get_redis_conn()
|
||||
@ -392,7 +399,7 @@ impl ParentPaymentMethodToken {
|
||||
redis_conn
|
||||
.set_key_with_expiry(
|
||||
&self.key_for_token,
|
||||
token,
|
||||
token_json_str,
|
||||
TOKEN_TTL - time_elapsed.whole_seconds(),
|
||||
)
|
||||
.await
|
||||
|
||||
@ -1,4 +1,44 @@
|
||||
use api_models::payment_methods;
|
||||
pub use diesel_models::payment_method::{
|
||||
PaymentMethod, PaymentMethodNew, PaymentMethodUpdate, PaymentMethodUpdateInternal,
|
||||
TokenizeCoreWorkflow,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PaymentTokenKind {
|
||||
Temporary,
|
||||
Permanent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CardTokenData {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GenericTokenData {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum PaymentTokenData {
|
||||
// The variants 'Temporary' and 'Permanent' are added for backwards compatibility
|
||||
// with any tokenized data present in Redis at the time of deployment of this change
|
||||
Temporary(GenericTokenData),
|
||||
TemporaryGeneric(GenericTokenData),
|
||||
Permanent(CardTokenData),
|
||||
PermanentCard(CardTokenData),
|
||||
AuthBankDebit(payment_methods::BankAccountConnectorDetails),
|
||||
}
|
||||
|
||||
impl PaymentTokenData {
|
||||
pub fn permanent_card(token: String) -> Self {
|
||||
Self::PermanentCard(CardTokenData { token })
|
||||
}
|
||||
|
||||
pub fn temporary_generic(token: String) -> Self {
|
||||
Self::TemporaryGeneric(GenericTokenData { token })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user