From 8e538cd6b3da4a155c55ce153982bff3c59ef575 Mon Sep 17 00:00:00 2001 From: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:42:55 +0530 Subject: [PATCH] feat(payment_methods_v2): Delete payment method api (#6211) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../src/payment_methods.rs | 22 +- crates/router/src/consts.rs | 20 ++ crates/router/src/core/payment_methods.rs | 118 ++++++++--- .../router/src/core/payment_methods/cards.rs | 189 ----------------- .../router/src/core/payment_methods/vault.rs | 192 ++++++++++++++++++ crates/router/src/routes/app.rs | 3 +- crates/router/src/routes/payment_methods.rs | 36 +++- crates/router/src/types/payment_methods.rs | 80 +++++--- 8 files changed, 410 insertions(+), 250 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/payment_methods.rs b/crates/hyperswitch_domain_models/src/payment_methods.rs index af3c657c34..445ad5ab80 100644 --- a/crates/hyperswitch_domain_models/src/payment_methods.rs +++ b/crates/hyperswitch_domain_models/src/payment_methods.rs @@ -14,6 +14,20 @@ use time::PrimitiveDateTime; use crate::type_encryption::{crypto_operation, AsyncLift, CryptoOperation}; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +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( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -67,7 +81,7 @@ pub struct PaymentMethod { pub payment_method_type: Option, pub metadata: Option, pub payment_method_data: OptionalEncryptableValue, - pub locker_id: Option, + pub locker_id: Option, pub last_used_at: PrimitiveDateTime, pub connector_mandate_details: Option, pub customer_acceptance: Option, @@ -302,7 +316,7 @@ impl super::behaviour::Conversion for PaymentMethod { payment_method_type: self.payment_method_type, metadata: self.metadata, payment_method_data: self.payment_method_data.map(|val| val.into()), - locker_id: self.locker_id, + locker_id: self.locker_id.map(|id| id.get_string_repr().clone()), last_used_at: self.last_used_at, connector_mandate_details: self.connector_mandate_details, customer_acceptance: self.customer_acceptance, @@ -356,7 +370,7 @@ impl super::behaviour::Conversion for PaymentMethod { .and_then(|val| val.try_into_optionaloperation()) }) .await?, - locker_id: item.locker_id, + locker_id: item.locker_id.map(VaultId::generate), last_used_at: item.last_used_at, connector_mandate_details: item.connector_mandate_details, customer_acceptance: item.customer_acceptance, @@ -415,7 +429,7 @@ impl super::behaviour::Conversion for PaymentMethod { payment_method_type: self.payment_method_type, metadata: self.metadata, payment_method_data: self.payment_method_data.map(|val| val.into()), - locker_id: self.locker_id, + locker_id: self.locker_id.map(|id| id.get_string_repr().clone()), last_used_at: self.last_used_at, connector_mandate_details: self.connector_mandate_details, customer_acceptance: self.customer_acceptance, diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 1b93f05d82..721dd9de1c 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -149,6 +149,26 @@ pub const VAULT_FINGERPRINT_REQUEST_URL: &str = "/fingerprint"; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub const VAULT_RETRIEVE_REQUEST_URL: &str = "/vault/retrieve"; +/// Vault Delete request url +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_DELETE_REQUEST_URL: &str = "/vault/delete"; + /// Vault Header content type #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub const VAULT_HEADER_CONTENT_TYPE: &str = "application/json"; + +/// Vault Add flow type +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_ADD_FLOW_TYPE: &str = "add_to_vault"; + +/// Vault Retrieve flow type +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_RETRIEVE_FLOW_TYPE: &str = "retrieve_from_vault"; + +/// Vault Delete flow type +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_DELETE_FLOW_TYPE: &str = "delete_from_vault"; + +/// Vault Fingerprint fetch flow type +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_GET_FINGERPRINT_FLOW_TYPE: &str = "get_fingerprint_vault"; diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 8841ad7f38..9a093a065e 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -1124,7 +1124,7 @@ pub async fn create_payment_method_in_db( req: &api::PaymentMethodCreate, customer_id: &id_type::CustomerId, payment_method_id: id_type::GlobalPaymentMethodId, - locker_id: Option, + locker_id: Option, merchant_id: &id_type::MerchantId, pm_metadata: Option, customer_acceptance: Option, @@ -1276,12 +1276,12 @@ pub async fn vault_payment_method( pmd: &pm_types::PaymentMethodVaultingData, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, - existing_vault_id: Option, + 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) + // get fingerprint_id from vault + let fingerprint_id_from_vault = vault::get_fingerprint_id_from_vault(state, pmd) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to get fingerprint_id from vault")?; @@ -1292,7 +1292,7 @@ pub async fn vault_payment_method( db.find_payment_method_by_fingerprint_id( &(state.into()), key_store, - &fingerprint_id_from_locker, + &fingerprint_id_from_vault, ) .await .change_context(errors::ApiErrorResponse::InternalServerError) @@ -1305,13 +1305,13 @@ pub async fn vault_payment_method( }, )?; - let resp_from_locker = - cards::vault_payment_method_in_locker(state, merchant_account, pmd, existing_vault_id) + let resp_from_vault = + vault::add_payment_method_to_vault(state, merchant_account, pmd, existing_vault_id) .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to vault payment method in locker")?; + .attach_printable("Failed to add payment method in vault")?; - Ok(resp_from_locker) + Ok(resp_from_vault) } #[cfg(all( @@ -1339,10 +1339,12 @@ async fn get_pm_list_context( storage::PaymentTokenData::permanent_card( Some(pm.get_id().clone()), pm.locker_id - .clone() + .as_ref() + .map(|id| id.get_string_repr().clone()) .or(Some(pm.get_id().get_string_repr().to_owned())), pm.locker_id - .clone() + .as_ref() + .map(|id| id.get_string_repr().clone()) .unwrap_or(pm.get_id().get_string_repr().to_owned()), ), ), @@ -1767,20 +1769,16 @@ pub async fn update_payment_method( }, )?; - 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 pmd: pm_types::PaymentMethodVaultingData = + vault::retrieve_payment_method_from_vault(&state, &merchant_account, &payment_method) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to retrieve payment method from vault")? + .data + .expose() + .parse_struct("PaymentMethodVaultingData") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse PaymentMethodVaultingData")?; let vault_request_data = pm_transforms::generate_pm_vaulting_req_from_update_request(pmd, req.payment_method_data); @@ -1825,6 +1823,76 @@ pub async fn update_payment_method( Ok(services::ApplicationResponse::Json(response)) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn delete_payment_method( + state: SessionState, + pm_id: api::PaymentMethodId, + key_store: domain::MerchantKeyStore, + merchant_account: domain::MerchantAccount, +) -> RouterResponse { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + + let pm_id = id_type::GlobalPaymentMethodId::generate_from_string(pm_id.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 vault_id = payment_method + .locker_id + .clone() + .get_required_value("locker_id") + .attach_printable("Missing locker_id in PaymentMethod")?; + + let _customer = db + .find_customer_by_global_id( + key_manager_state, + payment_method.customer_id.get_string_repr(), + merchant_account.get_id(), + &key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Customer not found for the payment method")?; + + // Soft delete + let pm_update = storage::PaymentMethodUpdate::StatusUpdate { + status: Some(enums::PaymentMethodStatus::Inactive), + }; + 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")?; + + vault::delete_payment_method_data_from_vault(&state, &merchant_account, vault_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to delete payment method from vault")?; + + let response = api::PaymentMethodDeleteResponse { + payment_method_id: pm_id.get_string_repr().to_string(), + }; + + 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 abcdf504e1..5da298815e 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -221,37 +221,6 @@ pub async fn create_payment_method( Ok(response) } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -async fn create_vault_request( - jwekey: &settings::Jwekey, - locker: &settings::Locker, - payload: Vec, -) -> errors::CustomResult { - let private_key = jwekey.vault_private_key.peek().as_bytes(); - - let jws = services::encryption::jws_sign_payload( - &payload, - &locker.locker_signing_key_id, - private_key, - ) - .await - .change_context(errors::VaultError::RequestEncryptionFailed)?; - - let jwe_payload = payment_methods::create_jwe_body_for_vault(jwekey, &jws).await?; - - let mut url = locker.host.to_owned(); - url.push_str(R::get_vaulting_request_url()); - let mut request = Request::new(services::Method::Post, &url); - request.add_header( - headers::CONTENT_TYPE, - router_consts::VAULT_HEADER_CONTENT_TYPE.into(), - ); - request.set_body(common_utils::request::RequestContent::Json(Box::new( - jwe_payload, - ))); - Ok(request) -} - #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -1411,107 +1380,6 @@ pub async fn add_payment_method( Ok(services::ApplicationResponse::Json(resp)) } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all)] -pub async fn get_fingerprint_id_from_locker< - D: pm_types::VaultingDataInterface + serde::Serialize, ->( - state: &routes::SessionState, - data: &D, -) -> errors::CustomResult { - let key = data.get_vaulting_data_key(); - let data = serde_json::to_value(data) - .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::VaultError::RequestEncodingFailed) - .attach_printable("Failed to encode VaultFingerprintRequest")?; - - let resp = call_to_vault::(state, payload) - .await - .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::VaultError::ResponseDeserializationFailed) - .attach_printable("Failed to parse data into VaultFingerprintResp")?; - - Ok(fingerprint_resp.fingerprint_id) -} - -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all)] -pub async fn vault_payment_method_in_locker( - state: &routes::SessionState, - merchant_account: &domain::MerchantAccount, - 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: 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::VaultError::RequestEncodingFailed) - .attach_printable("Failed to encode AddVaultRequest")?; - - 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::AddVaultResponse = resp - .parse_struct("AddVaultResponse") - .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") @@ -2266,37 +2134,6 @@ pub async fn add_card_to_hs_locker( Ok(stored_card) } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all)] -pub async fn call_to_vault( - state: &routes::SessionState, - payload: Vec, -) -> errors::CustomResult { - let locker = &state.conf.locker; - let jwekey = state.conf.jwekey.get_inner(); - - let request = create_vault_request::(jwekey, locker, payload).await?; - let response = services::call_connector_api(state, request, "vault_in_locker") - .await - .change_context(errors::VaultError::VaultAPIError); - - let jwe_body: services::JweBody = response - .get_response_inner("JweBody") - .change_context(errors::VaultError::ResponseDeserializationFailed) - .attach_printable("Failed to get JweBody from vault response")?; - - let decrypted_payload = payment_methods::get_decrypted_vault_response_payload( - jwekey, - jwe_body, - locker.decryption_scheme.clone(), - ) - .await - .change_context(errors::VaultError::ResponseDecryptionFailed) - .attach_printable("Error getting decrypted vault response payload")?; - - Ok(decrypted_payload) -} - #[instrument(skip_all)] pub async fn call_locker_api( state: &routes::SessionState, @@ -5401,21 +5238,6 @@ pub async fn retrieve_payment_method( )) } -#[cfg(all( - any(feature = "v2", feature = "v1"), - not(feature = "payment_methods_v2") -))] -#[instrument(skip_all)] -#[cfg(all(feature = "v2", feature = "customer_v2"))] -pub async fn delete_payment_method( - _state: routes::SessionState, - _merchant_account: domain::MerchantAccount, - _pm_id: api::PaymentMethodId, - _key_store: domain::MerchantKeyStore, -) -> errors::RouterResponse { - todo!() -} - #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[instrument(skip_all)] pub async fn delete_payment_method( @@ -5518,17 +5340,6 @@ pub async fn delete_payment_method( )) } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all)] -pub async fn delete_payment_method( - _state: routes::SessionState, - _merchant_account: domain::MerchantAccount, - _pm_id: api::PaymentMethodId, - _key_store: domain::MerchantKeyStore, -) -> errors::RouterResponse { - todo!() -} - pub async fn create_encrypted_data( state: &routes::SessionState, key_store: &domain::MerchantKeyStore, diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 75278010ce..7a24f2a30a 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1,4 +1,6 @@ use common_enums::PaymentMethodType; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use common_utils::request; use common_utils::{ crypto::{DecodeMessage, EncodeMessage, GcmAes256}, ext_traits::{BytesExt, Encode}, @@ -23,6 +25,11 @@ use crate::{ }, utils::StringExt, }; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use crate::{ + core::payment_methods::transformers as pm_transforms, headers, services, settings, + types::payment_methods as pm_types, utils::ConnectorResponseExt, +}; const VAULT_SERVICE_NAME: &str = "CARD"; pub struct SupplementaryVaultData { @@ -1172,6 +1179,191 @@ pub async fn delete_tokenized_data( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +async fn create_vault_request( + jwekey: &settings::Jwekey, + locker: &settings::Locker, + payload: Vec, +) -> CustomResult { + let private_key = jwekey.vault_private_key.peek().as_bytes(); + + let jws = services::encryption::jws_sign_payload( + &payload, + &locker.locker_signing_key_id, + private_key, + ) + .await + .change_context(errors::VaultError::RequestEncryptionFailed)?; + + let jwe_payload = pm_transforms::create_jwe_body_for_vault(jwekey, &jws).await?; + + let mut url = locker.host.to_owned(); + url.push_str(R::get_vaulting_request_url()); + let mut request = request::Request::new(services::Method::Post, &url); + request.add_header( + headers::CONTENT_TYPE, + consts::VAULT_HEADER_CONTENT_TYPE.into(), + ); + request.set_body(request::RequestContent::Json(Box::new(jwe_payload))); + Ok(request) +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn call_to_vault( + state: &routes::SessionState, + payload: Vec, +) -> CustomResult { + let locker = &state.conf.locker; + let jwekey = state.conf.jwekey.get_inner(); + + let request = create_vault_request::(jwekey, locker, payload).await?; + let response = services::call_connector_api(state, request, V::get_vaulting_flow_name()) + .await + .change_context(errors::VaultError::VaultAPIError); + + let jwe_body: services::JweBody = response + .get_response_inner("JweBody") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Failed to get JweBody from vault response")?; + + let decrypted_payload = pm_transforms::get_decrypted_vault_response_payload( + jwekey, + jwe_body, + locker.decryption_scheme.clone(), + ) + .await + .change_context(errors::VaultError::ResponseDecryptionFailed) + .attach_printable("Error getting decrypted vault response payload")?; + + Ok(decrypted_payload) +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn get_fingerprint_id_from_vault< + D: pm_types::VaultingDataInterface + serde::Serialize, +>( + state: &routes::SessionState, + data: &D, +) -> CustomResult { + let key = data.get_vaulting_data_key(); + let data = serde_json::to_string(data) + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode Vaulting data to string")?; + + let payload = pm_types::VaultFingerprintRequest { key, data } + .encode_to_vec() + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode VaultFingerprintRequest")?; + + let resp = call_to_vault::(state, payload) + .await + .change_context(errors::VaultError::VaultAPIError) + .attach_printable("Call to vault failed")?; + + let fingerprint_resp: pm_types::VaultFingerprintResponse = resp + .parse_struct("VaultFingerprintResponse") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Failed to parse data into VaultFingerprintResponse")?; + + Ok(fingerprint_resp.fingerprint_id) +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn add_payment_method_to_vault( + state: &routes::SessionState, + merchant_account: &domain::MerchantAccount, + pmd: &pm_types::PaymentMethodVaultingData, + existing_vault_id: Option, +) -> CustomResult { + let payload = pm_types::AddVaultRequest { + entity_id: merchant_account.get_id().to_owned(), + vault_id: existing_vault_id + .unwrap_or(domain::VaultId::generate(uuid::Uuid::now_v7().to_string())), + data: pmd, + ttl: state.conf.locker.ttl_for_storage_in_secs, + } + .encode_to_vec() + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode AddVaultRequest")?; + + let resp = call_to_vault::(state, payload) + .await + .change_context(errors::VaultError::VaultAPIError) + .attach_printable("Call to vault failed")?; + + let stored_pm_resp: pm_types::AddVaultResponse = resp + .parse_struct("AddVaultResponse") + .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, + pm: &domain::PaymentMethod, +) -> CustomResult { + let payload = pm_types::VaultRetrieveRequest { + entity_id: merchant_account.get_id().to_owned(), + vault_id: 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("Call to vault failed")?; + + 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(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn delete_payment_method_data_from_vault( + state: &routes::SessionState, + merchant_account: &domain::MerchantAccount, + vault_id: domain::VaultId, +) -> CustomResult { + let payload = pm_types::VaultDeleteRequest { + entity_id: merchant_account.get_id().to_owned(), + vault_id, + } + .encode_to_vec() + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode VaultDeleteRequest")?; + + let resp = call_to_vault::(state, payload) + .await + .change_context(errors::VaultError::VaultAPIError) + .attach_printable("Call to vault failed")?; + + let stored_pm_resp: pm_types::VaultDeleteResponse = resp + .parse_struct("VaultDeleteResponse") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Failed to parse data into VaultDeleteResponse")?; + + Ok(stored_pm_resp) +} + // ********************************************** PROCESS TRACKER ********************************************** pub async fn add_delete_tokenized_data_task( diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 54e4471b05..c267b113fc 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1091,7 +1091,8 @@ impl PaymentMethods { 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))); + .service(web::resource("/{id}").route(web::get().to(payment_method_retrieve_api))) + .service(web::resource("/{id}").route(web::delete().to(payment_method_delete_api))); route } diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 3e3aada154..77c072448a 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -14,8 +14,9 @@ use router_env::{instrument, logger, tracing, Flow}; 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, retrieve_payment_method, update_payment_method, + create_payment_method, delete_payment_method, list_customer_payment_method_util, + payment_method_intent_confirm, payment_method_intent_create, retrieve_payment_method, + update_payment_method, }; use crate::{ core::{ @@ -239,6 +240,33 @@ pub async fn payment_method_retrieve_api( .await } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsDelete))] +pub async fn payment_method_delete_api( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::PaymentMethodsDelete; + 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, _| { + delete_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, @@ -796,6 +824,10 @@ pub async fn payment_method_update_api( .await } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsDelete))] pub async fn payment_method_delete_api( state: web::Data, diff --git a/crates/router/src/types/payment_methods.rs b/crates/router/src/types/payment_methods.rs index a1c0f8156f..29290f0b7f 100644 --- a/crates/router/src/types/payment_methods.rs +++ b/crates/router/src/types/payment_methods.rs @@ -10,32 +10,17 @@ use crate::{ }; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] pub trait VaultingInterface { fn get_vaulting_request_url() -> &'static str; + + fn get_vaulting_flow_name() -> &'static str; } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] 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 { @@ -53,7 +38,7 @@ pub struct VaultFingerprintResponse { #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct AddVaultRequest { pub entity_id: common_utils::id_type::MerchantId, - pub vault_id: VaultId, + pub vault_id: domain::VaultId, pub data: D, pub ttl: i64, } @@ -62,7 +47,7 @@ pub struct AddVaultRequest { #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct AddVaultResponse { pub entity_id: common_utils::id_type::MerchantId, - pub vault_id: VaultId, + pub vault_id: domain::VaultId, pub fingerprint_id: Option, } @@ -79,27 +64,51 @@ pub struct GetVaultFingerprint; pub struct VaultRetrieve; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultDelete; + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl VaultingInterface for AddVault { fn get_vaulting_request_url() -> &'static str { consts::ADD_VAULT_REQUEST_URL } -} -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] -impl VaultingInterface for GetVaultFingerprint { - fn get_vaulting_request_url() -> &'static str { - consts::VAULT_FINGERPRINT_REQUEST_URL + fn get_vaulting_flow_name() -> &'static str { + consts::VAULT_ADD_FLOW_TYPE + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl VaultingInterface for GetVaultFingerprint { + fn get_vaulting_request_url() -> &'static str { + consts::VAULT_FINGERPRINT_REQUEST_URL + } + + fn get_vaulting_flow_name() -> &'static str { + consts::VAULT_GET_FINGERPRINT_FLOW_TYPE } } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] impl VaultingInterface for VaultRetrieve { fn get_vaulting_request_url() -> &'static str { consts::VAULT_RETRIEVE_REQUEST_URL } + + fn get_vaulting_flow_name() -> &'static str { + consts::VAULT_RETRIEVE_FLOW_TYPE + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl VaultingInterface for VaultDelete { + fn get_vaulting_request_url() -> &'static str { + consts::VAULT_DELETE_REQUEST_URL + } + + fn get_vaulting_flow_name() -> &'static str { + consts::VAULT_DELETE_FLOW_TYPE + } } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -109,7 +118,6 @@ pub enum PaymentMethodVaultingData { } #[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 { @@ -141,7 +149,7 @@ pub struct SavedPMLPaymentsInfo { #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct VaultRetrieveRequest { pub entity_id: common_utils::id_type::MerchantId, - pub vault_id: VaultId, + pub vault_id: domain::VaultId, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -149,3 +157,17 @@ pub struct VaultRetrieveRequest { pub struct VaultRetrieveResponse { pub data: Secret, } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultDeleteRequest { + pub entity_id: common_utils::id_type::MerchantId, + pub vault_id: domain::VaultId, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultDeleteResponse { + pub entity_id: common_utils::id_type::MerchantId, + pub vault_id: domain::VaultId, +}