mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +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 user::*; | ||||||
|  |  | ||||||
| pub use self::{ | pub use self::{ | ||||||
|     api_error_response::ApiErrorResponse, |     api_error_response::{ApiErrorResponse, NotImplementedMessage}, | ||||||
|     customers_error_response::CustomersErrorResponse, |     customers_error_response::CustomersErrorResponse, | ||||||
|     sch_errors::*, |     sch_errors::*, | ||||||
|     storage_errors::*, |     storage_errors::*, | ||||||
|  | |||||||
| @ -9,13 +9,17 @@ pub use api_models::{ | |||||||
| pub use common_utils::request::RequestBody; | pub use common_utils::request::RequestBody; | ||||||
| use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; | use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; | ||||||
| use diesel_models::enums; | use diesel_models::enums; | ||||||
|  | use error_stack::IntoReport; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     core::{errors::RouterResult, payments::helpers}, |     core::{ | ||||||
|  |         errors::{self, RouterResult}, | ||||||
|  |         payments::helpers, | ||||||
|  |     }, | ||||||
|     routes::AppState, |     routes::AppState, | ||||||
|     types::{ |     types::{ | ||||||
|         api::{self, payments}, |         api::{self, payments}, | ||||||
|         domain, |         domain, storage, | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @ -30,6 +34,14 @@ pub trait PaymentMethodRetrieve { | |||||||
|         payment_attempt: &PaymentAttempt, |         payment_attempt: &PaymentAttempt, | ||||||
|         merchant_key_store: &domain::MerchantKeyStore, |         merchant_key_store: &domain::MerchantKeyStore, | ||||||
|     ) -> RouterResult<(Option<payments::PaymentMethodData>, Option<String>)>; |     ) -> 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] | #[async_trait::async_trait] | ||||||
| @ -105,4 +117,65 @@ impl PaymentMethodRetrieve for Oss { | |||||||
|             _ => Ok((None, None)), |             _ => 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, |             self, | ||||||
|             types::{decrypt, encrypt_optional, AsyncLift}, |             types::{decrypt, encrypt_optional, AsyncLift}, | ||||||
|         }, |         }, | ||||||
|         storage::{self, enums}, |         storage::{self, enums, PaymentTokenData}, | ||||||
|         transformers::ForeignFrom, |         transformers::ForeignFrom, | ||||||
|     }, |     }, | ||||||
|     utils::{self, ConnectorResponseExt, OptionExt}, |     utils::{self, ConnectorResponseExt, OptionExt}, | ||||||
| @ -2103,23 +2103,32 @@ pub async fn list_customer_payment_method( | |||||||
|     let mut customer_pms = Vec::new(); |     let mut customer_pms = Vec::new(); | ||||||
|     for pm in resp.into_iter() { |     for pm in resp.into_iter() { | ||||||
|         let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token"); |         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 { |         let (card, pmd, hyperswitch_token_data) = match pm.payment_method { | ||||||
|             get_card_details(&pm, key, state, &hyperswitch_token, &key_store).await? |             enums::PaymentMethod::Card => ( | ||||||
|         } else { |                 Some(get_card_details(&pm, key, state).await?), | ||||||
|             None |                 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 |         //Need validation for enabled payment method ,querying MCA | ||||||
|         let pma = api::CustomerPaymentMethod { |         let pma = api::CustomerPaymentMethod { | ||||||
|             payment_token: parent_payment_method_token.to_owned(), |             payment_token: parent_payment_method_token.to_owned(), | ||||||
| @ -2134,10 +2143,7 @@ pub async fn list_customer_payment_method( | |||||||
|             installment_payment_enabled: false, |             installment_payment_enabled: false, | ||||||
|             payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), |             payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), | ||||||
|             created: Some(pm.created_at), |             created: Some(pm.created_at), | ||||||
|             #[cfg(feature = "payouts")] |  | ||||||
|             bank_transfer: pmd, |             bank_transfer: pmd, | ||||||
|             #[cfg(not(feature = "payouts"))] |  | ||||||
|             bank_transfer: None, |  | ||||||
|             requires_cvv, |             requires_cvv, | ||||||
|         }; |         }; | ||||||
|         customer_pms.push(pma.to_owned()); |         customer_pms.push(pma.to_owned()); | ||||||
| @ -2153,7 +2159,7 @@ pub async fn list_customer_payment_method( | |||||||
|             &parent_payment_method_token, |             &parent_payment_method_token, | ||||||
|             pma.payment_method, |             pma.payment_method, | ||||||
|         )) |         )) | ||||||
|         .insert(intent_created, hyperswitch_token, state) |         .insert(intent_created, hyperswitch_token_data, state) | ||||||
|         .await?; |         .await?; | ||||||
|  |  | ||||||
|         if let Some(metadata) = pma.metadata { |         if let Some(metadata) = pma.metadata { | ||||||
| @ -2200,10 +2206,8 @@ async fn get_card_details( | |||||||
|     pm: &payment_method::PaymentMethod, |     pm: &payment_method::PaymentMethod, | ||||||
|     key: &[u8], |     key: &[u8], | ||||||
|     state: &routes::AppState, |     state: &routes::AppState, | ||||||
|     hyperswitch_token: &str, | ) -> errors::RouterResult<api::CardDetailFromLocker> { | ||||||
|     key_store: &domain::MerchantKeyStore, |     let card_decrypted = | ||||||
| ) -> errors::RouterResult<Option<api::CardDetailFromLocker>> { |  | ||||||
|     let mut _card_decrypted = |  | ||||||
|         decrypt::<serde_json::Value, masking::WithType>(pm.payment_method_data.clone(), key) |         decrypt::<serde_json::Value, masking::WithType>(pm.payment_method_data.clone(), key) | ||||||
|             .await |             .await | ||||||
|             .change_context(errors::StorageError::DecryptionError) |             .change_context(errors::StorageError::DecryptionError) | ||||||
| @ -2217,16 +2221,17 @@ async fn get_card_details( | |||||||
|                 _ => None, |                 _ => None, | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|     Ok(Some( |     Ok(if let Some(mut crd) = card_decrypted { | ||||||
|         get_lookup_key_from_locker(state, hyperswitch_token, pm, key_store).await?, |         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, |     state: &routes::AppState, | ||||||
|     payment_token: &str, |  | ||||||
|     pm: &storage::PaymentMethod, |     pm: &storage::PaymentMethod, | ||||||
|     merchant_key_store: &domain::MerchantKeyStore, |  | ||||||
| ) -> errors::RouterResult<api::CardDetailFromLocker> { | ) -> errors::RouterResult<api::CardDetailFromLocker> { | ||||||
|     let card = get_card_from_locker( |     let card = get_card_from_locker( | ||||||
|         state, |         state, | ||||||
| @ -2237,9 +2242,19 @@ pub async fn get_lookup_key_from_locker( | |||||||
|     .await |     .await | ||||||
|     .change_context(errors::ApiErrorResponse::InternalServerError) |     .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|     .attach_printable("Error getting card from card vault")?; |     .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) |         .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 card = card_detail.clone(); | ||||||
|  |  | ||||||
|     let resp = TempLockerCardSupport::create_payment_method_data_in_temp_locker( |     let resp = TempLockerCardSupport::create_payment_method_data_in_temp_locker( | ||||||
|  | |||||||
| @ -55,7 +55,7 @@ use crate::{ | |||||||
|     utils::{ |     utils::{ | ||||||
|         self, |         self, | ||||||
|         crypto::{self, SignMessage}, |         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>( | pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>( | ||||||
|     operation: BoxedOperation<'a, F, R, Ctx>, |     operation: BoxedOperation<'a, F, R, Ctx>, | ||||||
|     state: &'a AppState, |     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 token = payment_data.token.clone(); | ||||||
|  |  | ||||||
|     let hyperswitch_token = match payment_data.mandate_id { |     let hyperswitch_token = match payment_data.mandate_id { | ||||||
|         Some(_) => token, |         Some(_) => token.map(storage::PaymentTokenData::temporary_generic), | ||||||
|         None => { |         None => { | ||||||
|             if let Some(token) = token { |             if let Some(token) = token { | ||||||
|                 let redis_conn = state |                 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")?, |                         .get_required_value("payment_method")?, | ||||||
|                 ); |                 ); | ||||||
|  |  | ||||||
|                 let key = redis_conn |                 let token_data_string = redis_conn | ||||||
|                     .get_key::<Option<String>>(&key) |                     .get_key::<Option<String>>(&key) | ||||||
|                     .await |                     .await | ||||||
|                     .change_context(errors::ApiErrorResponse::InternalServerError) |                     .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 { |             } else { | ||||||
|                 None |                 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. |     // TODO: Handle case where payment method and token both are present in request properly. | ||||||
|     let payment_method = match (request, hyperswitch_token) { |     let payment_method = match (request, hyperswitch_token) { | ||||||
|         (_, Some(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, |                 state, | ||||||
|                 &hyperswitch_token, |  | ||||||
|                 merchant_key_store, |                 merchant_key_store, | ||||||
|  |                 &hyperswitch_token, | ||||||
|  |                 &payment_data.payment_intent, | ||||||
|  |                 card_cvc, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|             .attach_printable( |             .attach_printable("in 'make_pm_data'")?; | ||||||
|                 "Payment method for given token not found or there was a problem fetching it", |  | ||||||
|             )?; |  | ||||||
|  |  | ||||||
|             utils::when( |             Ok::<_, error_stack::Report<errors::ApiErrorResponse>>( | ||||||
|                 supplementary_data |                 if let Some((payment_method_data, payment_method)) = payment_method_details { | ||||||
|                     .customer_id |                     payment_data.payment_attempt.payment_method = Some(payment_method); | ||||||
|                     .ne(&payment_data.payment_intent.customer_id), |                     Some(payment_method_data) | ||||||
|                 || { |                 } else { | ||||||
|                     Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payment method and customer passed in payment are not same".into() }) |                     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(_), _) => { |         (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 { |     if let Some(key_for_hyperswitch_token) = 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?; |             .await?; | ||||||
|     }; |     }; | ||||||
|     Ok(parent_payment_method_token) |     Ok(parent_payment_method_token) | ||||||
|  | |||||||
| @ -9,7 +9,11 @@ use super::app::AppState; | |||||||
| use crate::{ | use crate::{ | ||||||
|     core::{api_locking, errors, payment_methods::cards}, |     core::{api_locking, errors, payment_methods::cards}, | ||||||
|     services::{api, authentication as auth}, |     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 | /// PaymentMethods - Create | ||||||
| @ -379,9 +383,12 @@ impl ParentPaymentMethodToken { | |||||||
|     pub async fn insert( |     pub async fn insert( | ||||||
|         &self, |         &self, | ||||||
|         intent_created_at: Option<PrimitiveDateTime>, |         intent_created_at: Option<PrimitiveDateTime>, | ||||||
|         token: String, |         token: PaymentTokenData, | ||||||
|         state: &AppState, |         state: &AppState, | ||||||
|     ) -> CustomResult<(), errors::ApiErrorResponse> { |     ) -> 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 |         let redis_conn = state | ||||||
|             .store |             .store | ||||||
|             .get_redis_conn() |             .get_redis_conn() | ||||||
| @ -392,7 +399,7 @@ impl ParentPaymentMethodToken { | |||||||
|         redis_conn |         redis_conn | ||||||
|             .set_key_with_expiry( |             .set_key_with_expiry( | ||||||
|                 &self.key_for_token, |                 &self.key_for_token, | ||||||
|                 token, |                 token_json_str, | ||||||
|                 TOKEN_TTL - time_elapsed.whole_seconds(), |                 TOKEN_TTL - time_elapsed.whole_seconds(), | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|  | |||||||
| @ -1,4 +1,44 @@ | |||||||
|  | use api_models::payment_methods; | ||||||
| pub use diesel_models::payment_method::{ | pub use diesel_models::payment_method::{ | ||||||
|     PaymentMethod, PaymentMethodNew, PaymentMethodUpdate, PaymentMethodUpdateInternal, |     PaymentMethod, PaymentMethodNew, PaymentMethodUpdate, PaymentMethodUpdateInternal, | ||||||
|     TokenizeCoreWorkflow, |     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