mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +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
	 Shanks
					Shanks