From f096992218b760b3dd1e90922f6a1408752a7c24 Mon Sep 17 00:00:00 2001 From: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:45:18 +0530 Subject: [PATCH] feat(payment_methods_v2): Update and Retrieve payment method APIs for v2 (#5939) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/payment_methods.rs | 23 +- crates/router/src/consts.rs | 4 + crates/router/src/core/payment_methods.rs | 199 ++++++++++++++++-- .../router/src/core/payment_methods/cards.rs | 86 +++++--- .../src/core/payment_methods/transformers.rs | 34 +++ crates/router/src/core/payments/helpers.rs | 19 +- crates/router/src/routes/app.rs | 7 +- crates/router/src/routes/payment_methods.rs | 69 +++++- .../router/src/types/api/payment_methods.rs | 4 +- crates/router/src/types/payment_methods.rs | 54 ++++- 10 files changed, 432 insertions(+), 67 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 6c93b0bb5b..1a82dc0e26 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -374,7 +374,7 @@ pub struct PaymentMethodUpdate { #[serde(deny_unknown_fields)] pub struct PaymentMethodUpdate { /// payment method data to be passed - pub payment_method_data: Option, + pub payment_method_data: PaymentMethodUpdateData, /// This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK #[schema(max_length = 30, min_length = 30, example = "secret_k2uj3he2893eiu2d")] @@ -990,6 +990,27 @@ impl From for CardDetailFromLocker { } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for CardDetailFromLocker { + fn from(item: CardDetail) -> Self { + Self { + issuer_country: item.card_issuing_country, + last4_digits: Some(item.card_number.get_last4()), + card_number: Some(item.card_number), + expiry_month: Some(item.card_exp_month), + expiry_year: Some(item.card_exp_year), + card_holder_name: item.card_holder_name, + nick_name: item.nick_name, + card_isin: None, + card_issuer: item.card_issuer, + card_network: item.card_network, + card_type: item.card_type.map(|card| card.to_string()), + saved_to_locker: true, + card_fingerprint: None, + } + } +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl From for CardDetailsPaymentMethod { fn from(item: CardDetail) -> Self { diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 1f4fd00bbc..9e70cb2b96 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -144,6 +144,10 @@ pub const ADD_VAULT_REQUEST_URL: &str = "/vault/add"; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub const VAULT_FINGERPRINT_REQUEST_URL: &str = "/fingerprint"; +/// Vault Retrieve request url +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_RETRIEVE_REQUEST_URL: &str = "/vault/retrieve"; + /// Vault Header content type #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub const VAULT_HEADER_CONTENT_TYPE: &str = "application/json"; diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index cb26494b70..8841ad7f38 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -25,7 +25,7 @@ use common_utils::{consts::DEFAULT_LOCALE, id_type}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use common_utils::{ crypto::{self, Encryptable}, - ext_traits::{AsyncExt, Encode, ValueExt}, + ext_traits::{AsyncExt, Encode, StringExt, ValueExt}, fp_utils::when, generate_id, request::RequestContent, @@ -80,7 +80,7 @@ const PAYMENT_METHOD_STATUS_UPDATE_TASK: &str = "PAYMENT_METHOD_STATUS_UPDATE"; const PAYMENT_METHOD_STATUS_TAG: &str = "PAYMENT_METHOD_STATUS"; #[instrument(skip_all)] -pub async fn retrieve_payment_method( +pub async fn retrieve_payment_method_core( pm_data: &Option, state: &SessionState, payment_intent: &PaymentIntent, @@ -877,16 +877,24 @@ pub async fn create_payment_method( .await .attach_printable("Failed to add Payment method to DB")?; - let vaulting_result = - vault_payment_method(state, &req.payment_method_data, merchant_account, key_store).await; + let payment_method_data = pm_types::PaymentMethodVaultingData::from(req.payment_method_data); + + let vaulting_result = vault_payment_method( + state, + &payment_method_data, + merchant_account, + key_store, + None, + ) + .await; let response = match vaulting_result { Ok(resp) => { let pm_update = create_pm_additional_data_update( - &req.payment_method_data, + &payment_method_data, state, key_store, - Some(resp.vault_id), + Some(resp.vault_id.get_string_repr().clone()), Some(req.payment_method), Some(req.payment_method_type), ) @@ -1041,16 +1049,24 @@ pub async fn payment_method_intent_confirm( .await .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; - let vaulting_result = - vault_payment_method(state, &req.payment_method_data, merchant_account, key_store).await; + let payment_method_data = pm_types::PaymentMethodVaultingData::from(req.payment_method_data); + + let vaulting_result = vault_payment_method( + state, + &payment_method_data, + merchant_account, + key_store, + None, + ) + .await; let response = match vaulting_result { Ok(resp) => { let pm_update = create_pm_additional_data_update( - &req.payment_method_data, + &payment_method_data, state, key_store, - Some(resp.vault_id), + Some(resp.vault_id.get_string_repr().clone()), Some(req.payment_method), Some(req.payment_method_type), ) @@ -1220,15 +1236,17 @@ pub async fn create_payment_method_for_intent( #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn create_pm_additional_data_update( - pmd: &api::PaymentMethodCreateData, + pmd: &pm_types::PaymentMethodVaultingData, state: &SessionState, key_store: &domain::MerchantKeyStore, vault_id: Option, payment_method: Option, payment_method_type: Option, ) -> RouterResult { - let card = match pmd.clone() { - api::PaymentMethodCreateData::Card(card) => api::PaymentMethodsData::Card(card.into()), + let card = match pmd { + pm_types::PaymentMethodVaultingData::Card(card) => { + api::PaymentMethodsData::Card(card.clone().into()) + } }; let pmd: Encryptable> = @@ -1255,15 +1273,17 @@ pub async fn create_pm_additional_data_update( #[instrument(skip_all)] pub async fn vault_payment_method( state: &SessionState, - pmd: &api::PaymentMethodCreateData, + pmd: &pm_types::PaymentMethodVaultingData, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, + existing_vault_id: Option, ) -> RouterResult { let db = &*state.store; // get fingerprint_id from locker let fingerprint_id_from_locker = cards::get_fingerprint_id_from_locker(state, pmd) .await + .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to get fingerprint_id from vault")?; // throw back error if payment method is duplicated @@ -1286,7 +1306,10 @@ pub async fn vault_payment_method( )?; let resp_from_locker = - cards::vault_payment_method_in_locker(state, merchant_account, pmd).await?; + cards::vault_payment_method_in_locker(state, merchant_account, pmd, existing_vault_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to vault payment method in locker")?; Ok(resp_from_locker) } @@ -1656,6 +1679,152 @@ async fn generate_saved_pm_response( Ok(pma) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn retrieve_payment_method( + state: SessionState, + pm: api::PaymentMethodId, + key_store: domain::MerchantKeyStore, + merchant_account: domain::MerchantAccount, +) -> RouterResponse { + let db = state.store.as_ref(); + let pm_id = id_type::GlobalPaymentMethodId::generate_from_string(pm.payment_method_id) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to generate GlobalPaymentMethodId")?; + + let payment_method = db + .find_payment_method( + &((&state).into()), + &key_store, + &pm_id, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + + let pmd = payment_method + .payment_method_data + .clone() + .map(|x| x.into_inner().expose()) + .and_then(|v| serde_json::from_value::(v).ok()) + .and_then(|pmd| match pmd { + api::PaymentMethodsData::Card(card) => { + Some(api::PaymentMethodResponseData::Card(card.into())) + } + _ => None, + }); + + let resp = api::PaymentMethodResponse { + merchant_id: payment_method.merchant_id.to_owned(), + customer_id: payment_method.customer_id.to_owned(), + payment_method_id: payment_method.id.get_string_repr().to_string(), + payment_method: payment_method.payment_method, + payment_method_type: payment_method.payment_method_type, + metadata: payment_method.metadata.clone(), + created: Some(payment_method.created_at), + recurring_enabled: false, + last_used_at: Some(payment_method.last_used_at), + client_secret: payment_method.client_secret.clone(), + payment_method_data: pmd, + }; + + Ok(services::ApplicationResponse::Json(resp)) +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn update_payment_method( + state: SessionState, + merchant_account: domain::MerchantAccount, + req: api::PaymentMethodUpdate, + payment_method_id: &str, + key_store: domain::MerchantKeyStore, +) -> RouterResponse { + let db = state.store.as_ref(); + + let pm_id = id_type::GlobalPaymentMethodId::generate_from_string(payment_method_id.to_string()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to generate GlobalPaymentMethodId")?; + + let payment_method = db + .find_payment_method( + &((&state).into()), + &key_store, + &pm_id, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + let current_vault_id = payment_method.locker_id.clone(); + + when( + payment_method.status == enums::PaymentMethodStatus::AwaitingData, + || { + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "This Payment method is awaiting data and hence cannot be updated" + .to_string(), + }) + }, + )?; + + let pmd: pm_types::PaymentMethodVaultingData = cards::retrieve_payment_method_from_vault( + &state, + &merchant_account, + &payment_method.customer_id, + &payment_method, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to retrieve payment method from vault")? + .data + .expose() + .parse_struct("PaymentMethodCreateData") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse PaymentMethodCreateData")?; + + let vault_request_data = + pm_transforms::generate_pm_vaulting_req_from_update_request(pmd, req.payment_method_data); + + let vaulting_response = vault_payment_method( + &state, + &vault_request_data, + &merchant_account, + &key_store, + current_vault_id, // using current vault_id for now, will have to refactor this + ) // to generate new one on each vaulting later on + .await + .attach_printable("Failed to add payment method in vault")?; + + let pm_update = create_pm_additional_data_update( + &vault_request_data, + &state, + &key_store, + Some(vaulting_response.vault_id.get_string_repr().clone()), + payment_method.payment_method, + payment_method.payment_method_type, + ) + .await + .attach_printable("Unable to create Payment method data")?; + + let payment_method = db + .update_payment_method( + &((&state).into()), + &key_store, + payment_method, + pm_update, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update payment method in db")?; + + let response = pm_transforms::generate_payment_method_response(&payment_method)?; + + // Add a PT task to handle payment_method delete from vault + + Ok(services::ApplicationResponse::Json(response)) +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl pm_types::SavedPMLPaymentsInfo { pub async fn form_payments_info( diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index a7ae7f9ca5..578655a927 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1418,26 +1418,26 @@ pub async fn get_fingerprint_id_from_locker< >( state: &routes::SessionState, data: &D, -) -> errors::RouterResult { +) -> errors::CustomResult { let key = data.get_vaulting_data_key(); let data = serde_json::to_value(data) - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::VaultError::RequestEncodingFailed) .attach_printable("Failed to encode Vaulting data to value")? .to_string(); let payload = pm_types::VaultFingerprintRequest { key, data } .encode_to_vec() - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::VaultError::RequestEncodingFailed) .attach_printable("Failed to encode VaultFingerprintRequest")?; let resp = call_to_vault::(state, payload) .await - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::VaultError::VaultAPIError) .attach_printable("Failed to get response from locker")?; let fingerprint_resp: pm_types::VaultFingerprintResponse = resp .parse_struct("VaultFingerprintResp") - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::VaultError::ResponseDeserializationFailed) .attach_printable("Failed to parse data into VaultFingerprintResp")?; Ok(fingerprint_resp.fingerprint_id) @@ -1448,31 +1448,70 @@ pub async fn get_fingerprint_id_from_locker< pub async fn vault_payment_method_in_locker( state: &routes::SessionState, merchant_account: &domain::MerchantAccount, - pmd: &api::PaymentMethodCreateData, -) -> errors::RouterResult { + pmd: &pm_types::PaymentMethodVaultingData, + existing_vault_id: Option, +) -> errors::CustomResult { let payload = pm_types::AddVaultRequest { entity_id: merchant_account.get_id().to_owned(), - vault_id: uuid::Uuid::now_v7().to_string(), - data: pmd.clone(), + vault_id: pm_types::VaultId::generate( + existing_vault_id.unwrap_or(uuid::Uuid::now_v7().to_string()), + ), + data: pmd, ttl: state.conf.locker.ttl_for_storage_in_secs, } .encode_to_vec() - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::VaultError::RequestEncodingFailed) .attach_printable("Failed to encode AddVaultRequest")?; let resp = call_to_vault::(state, payload) .await - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::VaultError::VaultAPIError) .attach_printable("Failed to get response from locker")?; let stored_pm_resp: pm_types::AddVaultResponse = resp .parse_struct("AddVaultResponse") - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::VaultError::ResponseDeserializationFailed) .attach_printable("Failed to parse data into AddVaultResponse")?; Ok(stored_pm_resp) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn retrieve_payment_method_from_vault( + state: &routes::SessionState, + merchant_account: &domain::MerchantAccount, + customer_id: &id_type::CustomerId, + pm: &domain::PaymentMethod, +) -> errors::CustomResult { + let payload = pm_types::VaultRetrieveRequest { + entity_id: merchant_account.get_id().to_owned(), + vault_id: pm_types::VaultId::generate( + pm.locker_id + .clone() + .ok_or(errors::VaultError::MissingRequiredField { + field_name: "locker_id", + }) + .attach_printable("Missing locker_id for VaultRetrieveRequest")?, + ), + } + .encode_to_vec() + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode VaultRetrieveRequest")?; + + let resp = call_to_vault::(state, payload) + .await + .change_context(errors::VaultError::VaultAPIError) + .attach_printable("Failed to get response from locker")?; + + let stored_pm_resp: pm_types::VaultRetrieveResponse = resp + .parse_struct("VaultRetrieveResponse") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Failed to parse data into VaultRetrieveResponse")?; + + Ok(stored_pm_resp) +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -1783,18 +1822,6 @@ pub async fn update_customer_payment_method( } } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all)] -pub async fn update_customer_payment_method( - _state: routes::SessionState, - _merchant_account: domain::MerchantAccount, - _req: api::PaymentMethodUpdate, - _payment_method_id: &str, - _key_store: domain::MerchantKeyStore, -) -> errors::RouterResponse { - todo!() -} - #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -5378,17 +5405,6 @@ pub async fn retrieve_payment_method( )) } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all)] -pub async fn retrieve_payment_method( - state: routes::SessionState, - pm: api::PaymentMethodId, - key_store: domain::MerchantKeyStore, - merchant_account: domain::MerchantAccount, -) -> errors::RouterResponse { - todo!() -} - #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index ab21add60a..78770c3457 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -15,6 +15,8 @@ use josekit::jwe; use router_env::tracing_actix_web::RequestId; use serde::{Deserialize, Serialize}; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use crate::types::payment_methods as pm_types; use crate::{ configs::settings, core::errors::{self, CustomResult}, @@ -520,6 +522,29 @@ pub fn mk_add_card_response_hs( todo!() } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub fn generate_pm_vaulting_req_from_update_request( + pm_create: pm_types::PaymentMethodVaultingData, + pm_update: api::PaymentMethodUpdateData, +) -> pm_types::PaymentMethodVaultingData { + match (pm_create, pm_update) { + ( + pm_types::PaymentMethodVaultingData::Card(card_create), + api::PaymentMethodUpdateData::Card(update_card), + ) => pm_types::PaymentMethodVaultingData::Card(api::CardDetail { + card_number: card_create.card_number, + card_exp_month: card_create.card_exp_month, + card_exp_year: card_create.card_exp_year, + card_issuing_country: card_create.card_issuing_country, + card_network: card_create.card_network, + card_issuer: card_create.card_issuer, + card_type: card_create.card_type, + card_holder_name: update_card.card_holder_name, + nick_name: update_card.nick_name, + }), + } +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub fn generate_payment_method_response( pm: &domain::PaymentMethod, @@ -798,6 +823,15 @@ pub fn get_card_detail( Ok(card_detail) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for pm_types::PaymentMethodVaultingData { + fn from(item: api::PaymentMethodCreateData) -> Self { + match item { + api::PaymentMethodCreateData::Card(card) => Self::Card(card), + } + } +} + //------------------------------------------------TokenizeService------------------------------------------------ pub fn mk_crud_locker_request( locker: &settings::Locker, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 483c8c56ac..a2fbb3bae4 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2226,15 +2226,16 @@ pub async fn make_pm_data<'a, F: Clone, R, D>( } (Some(_), _) => { - let (payment_method_data, payment_token) = payment_methods::retrieve_payment_method( - &request, - state, - &payment_data.payment_intent, - &payment_data.payment_attempt, - merchant_key_store, - Some(business_profile), - ) - .await?; + let (payment_method_data, payment_token) = + payment_methods::retrieve_payment_method_core( + &request, + state, + &payment_data.payment_intent, + &payment_data.payment_attempt, + merchant_key_store, + Some(business_profile), + ) + .await?; payment_data.token = payment_token; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 9ff768c56f..d5f73921a9 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1087,7 +1087,12 @@ impl PaymentMethods { .service( web::resource("/{id}/confirm-intent") .route(web::post().to(confirm_payment_method_intent_api)), - ); + ) + .service( + web::resource("/{id}/update_saved_payment_method") + .route(web::patch().to(payment_method_update_api)), + ) + .service(web::resource("/{id}").route(web::get().to(payment_method_retrieve_api))); route } diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 725d6af81a..3e3aada154 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -15,7 +15,7 @@ use super::app::{AppState, SessionState}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use crate::core::payment_methods::{ create_payment_method, list_customer_payment_method_util, payment_method_intent_confirm, - payment_method_intent_create, + payment_method_intent_create, retrieve_payment_method, update_payment_method, }; use crate::{ core::{ @@ -180,6 +180,65 @@ pub async fn confirm_payment_method_intent_api( .await } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsUpdate))] +pub async fn payment_method_update_api( + state: web::Data, + req: HttpRequest, + path: web::Path, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::PaymentMethodsUpdate; + let payment_method_id = path.into_inner(); + let payload = json_payload.into_inner(); + + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, req, _| { + update_payment_method( + state, + auth.merchant_account, + req, + &payment_method_id, + auth.key_store, + ) + }, + &auth::HeaderAuth(auth::ApiKeyAuth), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsRetrieve))] +pub async fn payment_method_retrieve_api( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::PaymentMethodsRetrieve; + let payload = web::Json(PaymentMethodId { + payment_method_id: path.into_inner(), + }) + .into_inner(); + + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, pm, _| { + retrieve_payment_method(state, pm, auth.key_store, auth.merchant_account) + }, + &auth::HeaderAuth(auth::ApiKeyAuth), + api_locking::LockAction::NotApplicable, + )) + .await +} + #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsMigrate))] pub async fn migrate_payment_method_api( state: web::Data, @@ -667,6 +726,10 @@ pub async fn render_pm_collect_link( .await } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsRetrieve))] pub async fn payment_method_retrieve_api( state: web::Data, @@ -693,6 +756,10 @@ pub async fn payment_method_retrieve_api( .await } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsUpdate))] pub async fn payment_method_update_api( state: web::Data, diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 28d387771b..d66bf079d4 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -8,8 +8,8 @@ pub use api_models::payment_methods::{ PaymentMethodIntentConfirm, PaymentMethodIntentConfirmInternal, PaymentMethodIntentCreate, PaymentMethodList, PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate, PaymentMethodResponse, PaymentMethodResponseData, PaymentMethodUpdate, - PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, - TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, + PaymentMethodUpdateData, PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest, + TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, }; #[cfg(all( any(feature = "v2", feature = "v1"), diff --git a/crates/router/src/types/payment_methods.rs b/crates/router/src/types/payment_methods.rs index e55516fc3a..a1c0f8156f 100644 --- a/crates/router/src/types/payment_methods.rs +++ b/crates/router/src/types/payment_methods.rs @@ -1,5 +1,7 @@ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use common_utils::generate_id; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use masking::Secret; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use crate::{ @@ -19,6 +21,21 @@ pub trait VaultingDataInterface { fn get_vaulting_data_key(&self) -> String; } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultId(String); + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl VaultId { + pub fn get_string_repr(&self) -> &String { + &self.0 + } + + pub fn generate(id: String) -> Self { + Self(id) + } +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct VaultFingerprintRequest { @@ -36,7 +53,7 @@ pub struct VaultFingerprintResponse { #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct AddVaultRequest { pub entity_id: common_utils::id_type::MerchantId, - pub vault_id: String, + pub vault_id: VaultId, pub data: D, pub ttl: i64, } @@ -45,7 +62,7 @@ pub struct AddVaultRequest { #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct AddVaultResponse { pub entity_id: common_utils::id_type::MerchantId, - pub vault_id: String, + pub vault_id: VaultId, pub fingerprint_id: Option, } @@ -57,6 +74,10 @@ pub struct AddVault; #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct GetVaultFingerprint; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultRetrieve; + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[async_trait::async_trait] impl VaultingInterface for AddVault { @@ -75,7 +96,21 @@ impl VaultingInterface for GetVaultFingerprint { #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[async_trait::async_trait] -impl VaultingDataInterface for api::PaymentMethodCreateData { +impl VaultingInterface for VaultRetrieve { + fn get_vaulting_request_url() -> &'static str { + consts::VAULT_RETRIEVE_REQUEST_URL + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub enum PaymentMethodVaultingData { + Card(api::CardDetail), +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[async_trait::async_trait] +impl VaultingDataInterface for PaymentMethodVaultingData { fn get_vaulting_data_key(&self) -> String { match &self { Self::Card(card) => card.card_number.to_string(), @@ -101,3 +136,16 @@ pub struct SavedPMLPaymentsInfo { pub off_session_payment_flag: bool, pub is_connector_agnostic_mit_enabled: bool, } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultRetrieveRequest { + pub entity_id: common_utils::id_type::MerchantId, + pub vault_id: VaultId, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultRetrieveResponse { + pub data: Secret, +}