diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 7c18534c5a..9b94bc0d8b 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -6,6 +6,7 @@ use utoipa::ToSchema; pub use self::CreateMerchantAccount as MerchantAccountResponse; use super::payments::AddressDetails; use crate::{enums as api_enums, payment_methods}; + #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] #[serde(deny_unknown_fields)] pub struct CreateMerchantAccount { diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index ef87f910b7..858ddf8023 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -289,3 +289,14 @@ pub struct TokenizedCardValue2 { pub customer_id: Option, pub payment_method_id: Option, } + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TokenizedWalletValue1 { + pub issuer: String, + pub token: Option, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TokenizedWalletValue2 { + pub customer_id: Option, +} diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 53e6ace8d2..2ec3cb1503 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -243,7 +243,7 @@ pub enum ConnectorError { } #[derive(Debug, thiserror::Error)] -pub enum CardVaultError { +pub enum VaultError { #[error("Failed to save card in card vault")] SaveCardFailed, #[error("Failed to fetch card details from card vault")] @@ -254,6 +254,8 @@ pub enum CardVaultError { ResponseDeserializationFailed, #[error("Failed to create payment method")] PaymentMethodCreationFailed, + #[error("The given payment method is currently not supported in vault")] + PaymentMethodNotSupported, #[error("Missing required field: {field_name}")] MissingRequiredField { field_name: String }, #[error("The card vault returned an unexpected response: {0:?}")] diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 93d7e02861..f1d641adbe 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -1,2 +1,3 @@ pub mod cards; pub mod transformers; +pub mod vault; diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 95642a771f..0181b89eab 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -7,7 +7,7 @@ use router_env::{instrument, tracing}; use crate::{ core::{ errors::{self, StorageErrorExt}, - payment_methods::transformers as payment_methods, + payment_methods::{transformers as payment_methods, vault}, payments::helpers, }, db, @@ -18,7 +18,7 @@ use crate::{ storage::{self, enums}, transformers::ForeignInto, }, - utils::{BytesExt, OptionExt, StringExt}, + utils::{BytesExt, OptionExt}, }; #[instrument(skip_all)] @@ -133,7 +133,7 @@ pub async fn add_card( card: api::CardDetail, customer_id: String, merchant_account: &storage::MerchantAccount, -) -> errors::CustomResult { +) -> errors::CustomResult { let locker = &state.conf.locker; let db = &*state.store; let merchant_id = &merchant_account.merchant_id; @@ -141,7 +141,7 @@ pub async fn add_card( .locker_id .to_owned() .get_required_value("locker_id") - .change_context(errors::CardVaultError::SaveCardFailed)?; + .change_context(errors::VaultError::SaveCardFailed)?; let request = payment_methods::mk_add_card_request( locker, @@ -154,14 +154,14 @@ pub async fn add_card( let response = if !locker.mock_locker { let response = services::call_connector_api(state, request) .await - .change_context(errors::CardVaultError::SaveCardFailed)?; + .change_context(errors::VaultError::SaveCardFailed)?; let response: payment_methods::AddCardResponse = match response { Ok(card) => card .response .parse_struct("AddCardResponse") - .change_context(errors::CardVaultError::ResponseDeserializationFailed), - Err(err) => Err(report!(errors::CardVaultError::UnexpectedResponseError( + .change_context(errors::VaultError::ResponseDeserializationFailed), + Err(err) => Err(report!(errors::VaultError::UnexpectedResponseError( err.response ))), }?; @@ -174,7 +174,7 @@ pub async fn add_card( if let Some(false) = response.duplicate { create_payment_method(db, &req, &customer_id, &response.card_id, merchant_id) .await - .change_context(errors::CardVaultError::PaymentMethodCreationFailed)?; + .change_context(errors::VaultError::PaymentMethodCreationFailed)?; } else { match db.find_payment_method(&response.card_id).await { Ok(_) => (), @@ -182,9 +182,9 @@ pub async fn add_card( if err.current_context().is_db_not_found() { create_payment_method(db, &req, &customer_id, &response.card_id, merchant_id) .await - .change_context(errors::CardVaultError::PaymentMethodCreationFailed)?; + .change_context(errors::VaultError::PaymentMethodCreationFailed)?; } else { - Err(errors::CardVaultError::PaymentMethodCreationFailed)?; + Err(errors::VaultError::PaymentMethodCreationFailed)?; } } } @@ -202,7 +202,7 @@ pub async fn mock_add_card( card_cvc: Option, payment_method_id: Option, customer_id: Option<&str>, -) -> errors::CustomResult { +) -> errors::CustomResult { let locker_mock_up = storage::LockerMockUpNew { card_id: card_id.to_string(), external_id: uuid::Uuid::new_v4().to_string(), @@ -220,7 +220,7 @@ pub async fn mock_add_card( let response = db .insert_locker_mock_up(locker_mock_up) .await - .change_context(errors::CardVaultError::SaveCardFailed)?; + .change_context(errors::VaultError::SaveCardFailed)?; Ok(payment_methods::AddCardResponse { card_id: response.card_id, external_id: response.external_id, @@ -241,12 +241,11 @@ pub async fn mock_add_card( pub async fn mock_get_card<'a>( db: &dyn db::StorageInterface, card_id: &'a str, -) -> errors::CustomResult<(payment_methods::GetCardResponse, Option), errors::CardVaultError> -{ +) -> errors::CustomResult<(payment_methods::GetCardResponse, Option), errors::VaultError> { let locker_mock_up = db .find_locker_by_card_id(card_id) .await - .change_context(errors::CardVaultError::FetchCardFailed)?; + .change_context(errors::VaultError::FetchCardFailed)?; let add_card_response = payment_methods::AddCardResponse { card_id: locker_mock_up .payment_method_id @@ -275,11 +274,11 @@ pub async fn mock_get_card<'a>( pub async fn mock_delete_card<'a>( db: &dyn db::StorageInterface, card_id: &'a str, -) -> errors::CustomResult { +) -> errors::CustomResult { let locker_mock_up = db .delete_locker_mock_up(card_id) .await - .change_context(errors::CardVaultError::FetchCardFailed)?; + .change_context(errors::VaultError::FetchCardFailed)?; Ok(payment_methods::DeleteCardResponse { card_id: locker_mock_up.card_id, external_id: locker_mock_up.external_id, @@ -783,7 +782,7 @@ impl BasiliskCardSupport { ) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error getting Value2 for locker")?; - create_tokenize(state, value1, Some(value2), payment_token.to_string()).await?; + vault::create_tokenize(state, value1, Some(value2), payment_token.to_string()).await?; Ok(card) } } @@ -840,9 +839,9 @@ pub async fn delete_payment_method( merchant_account: storage::MerchantAccount, pm: api::PaymentMethodId, ) -> errors::RouterResponse { - let (_, value2) = - helpers::Vault::get_payment_method_data_from_locker(state, &pm.payment_method_id).await?; - let payment_method_id = value2 + let (_, supplementary_data) = + vault::Vault::get_payment_method_data_from_locker(state, &pm.payment_method_id).await?; + let payment_method_id = supplementary_data .payment_method_id .map_or(Err(errors::ApiErrorResponse::PaymentMethodNotFound), Ok)?; let pm = state @@ -872,174 +871,3 @@ pub async fn delete_payment_method( }, )) } - -//------------------------------------------------TokenizeService------------------------------------------------ -pub async fn create_tokenize( - state: &routes::AppState, - value1: String, - value2: Option, - lookup_key: String, -) -> errors::RouterResult { - let payload_to_be_encrypted = api::TokenizePayloadRequest { - value1, - value2: value2.unwrap_or_default(), - lookup_key, - service_name: "CARD".to_string(), - }; - let payload = serde_json::to_string(&payload_to_be_encrypted) - .map_err(|_x| errors::ApiErrorResponse::InternalServerError)?; - let encrypted_payload = services::encrypt_jwe(&state.conf.jwekey, &payload) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encrypt JWE response")?; - - let create_tokenize_request = api::TokenizePayloadEncrypted { - payload: encrypted_payload, - key_id: services::get_key_id(&state.conf.jwekey).to_string(), - version: Some("0".to_string()), - }; - let request = payment_methods::mk_crud_locker_request( - &state.conf.locker, - "/tokenize", - create_tokenize_request, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Making tokenize request failed")?; - let response = services::call_connector_api(state, request) - .await - .change_context(errors::ApiErrorResponse::InternalServerError)?; - - match response { - Ok(r) => { - let resp: api::TokenizePayloadEncrypted = r - .response - .parse_struct("TokenizePayloadEncrypted") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decoding Failed for TokenizePayloadEncrypted")?; - let decrypted_payload = - services::decrypt_jwe(&state.conf.jwekey, &resp.payload, &resp.key_id) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decrypt Jwe failed for TokenizePayloadEncrypted")?; - let get_response: api::GetTokenizePayloadResponse = decrypted_payload - .parse_struct("GetTokenizePayloadResponse") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Error getting GetTokenizePayloadResponse from tokenize response", - )?; - Ok(get_response.lookup_key) - } - Err(err) => Err(report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable(format!("Got 4xx from the basilisk locker: {err:?}"))), - } -} - -pub async fn get_tokenized_data( - state: &routes::AppState, - lookup_key: &str, - should_get_value2: bool, -) -> errors::RouterResult { - let payload_to_be_encrypted = api::GetTokenizePayloadRequest { - lookup_key: lookup_key.to_string(), - get_value2: should_get_value2, - }; - let payload = serde_json::to_string(&payload_to_be_encrypted) - .map_err(|_x| errors::ApiErrorResponse::InternalServerError)?; - let encrypted_payload = services::encrypt_jwe(&state.conf.jwekey, &payload) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encrypt JWE response")?; - let create_tokenize_request = api::TokenizePayloadEncrypted { - payload: encrypted_payload, - key_id: services::get_key_id(&state.conf.jwekey).to_string(), - version: Some("0".to_string()), - }; - let request = payment_methods::mk_crud_locker_request( - &state.conf.locker, - "/tokenize/get", - create_tokenize_request, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Making Get Tokenized request failed")?; - let response = services::call_connector_api(state, request) - .await - .change_context(errors::ApiErrorResponse::InternalServerError)?; - match response { - Ok(r) => { - let resp: api::TokenizePayloadEncrypted = r - .response - .parse_struct("TokenizePayloadEncrypted") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decoding Failed for TokenizePayloadEncrypted")?; - let decrypted_payload = - services::decrypt_jwe(&state.conf.jwekey, &resp.payload, &resp.key_id) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "GetTokenizedApi: Decrypt Jwe failed for TokenizePayloadEncrypted", - )?; - let get_response: api::TokenizePayloadRequest = decrypted_payload - .parse_struct("TokenizePayloadRequest") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting TokenizePayloadRequest from tokenize response")?; - Ok(get_response) - } - Err(err) => Err(report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable(format!("Got 4xx from the basilisk locker: {err:?}"))), - } -} - -pub async fn delete_tokenized_data( - state: &routes::AppState, - lookup_key: &str, -) -> errors::RouterResult { - let payload_to_be_encrypted = api::DeleteTokenizeByTokenRequest { - lookup_key: lookup_key.to_string(), - }; - let payload = serde_json::to_string(&payload_to_be_encrypted) - .map_err(|_x| errors::ApiErrorResponse::InternalServerError)?; - let encrypted_payload = services::encrypt_jwe(&state.conf.jwekey, &payload) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encrypt JWE response")?; - let create_tokenize_request = api::TokenizePayloadEncrypted { - payload: encrypted_payload, - key_id: services::get_key_id(&state.conf.jwekey).to_string(), - version: Some("0".to_string()), - }; - let request = payment_methods::mk_crud_locker_request( - &state.conf.locker, - "/tokenize/delete/token", - create_tokenize_request, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Making Delete Tokenized request failed")?; - let response = services::call_connector_api(state, request) - .await - .change_context(errors::ApiErrorResponse::InternalServerError)?; - match response { - Ok(r) => { - let resp: api::TokenizePayloadEncrypted = r - .response - .parse_struct("TokenizePayloadEncrypted") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decoding Failed for TokenizePayloadEncrypted")?; - let decrypted_payload = - services::decrypt_jwe(&state.conf.jwekey, &resp.payload, &resp.key_id) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "DeleteTokenizedApi: Decrypt Jwe failed for TokenizePayloadEncrypted", - )?; - let delete_response = decrypted_payload - .parse_struct("Delete") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Error getting TokenizePayloadEncrypted from tokenize response", - )?; - Ok(delete_response) - } - Err(err) => Err(report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable(format!("Got 4xx from the basilisk locker: {err:?}"))), - } -} diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index beb77187a3..6b1b7126f5 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -70,7 +70,7 @@ pub fn mk_add_card_request( _req: &api::CreatePaymentMethod, locker_id: &str, _merchant_id: &str, -) -> CustomResult { +) -> CustomResult { #[cfg(feature = "sandbox")] let customer_id = &format!("{}::{}", customer_id, _merchant_id); let add_card_req = AddCardRequest { @@ -84,7 +84,7 @@ pub fn mk_add_card_request( nickname: Some("router".to_string()), // }; let body = utils::Encode::>::encode(&add_card_req) - .change_context(errors::CardVaultError::RequestEncodingFailed)?; + .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/addCard"); let mut request = services::Request::new(services::Method::Post, &url); @@ -134,14 +134,14 @@ pub fn mk_get_card_request<'a>( locker: &Locker, locker_id: &'a str, card_id: &'a str, -) -> CustomResult { +) -> CustomResult { let get_card_req = GetCard { merchant_id: locker_id, card_id, }; let body = utils::Encode::>::encode(&get_card_req) - .change_context(errors::CardVaultError::RequestEncodingFailed)?; + .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/getCard"); let mut request = services::Request::new(services::Method::Post, &url); @@ -154,13 +154,13 @@ pub fn mk_delete_card_request<'a>( locker: &Locker, merchant_id: &'a str, card_id: &'a str, -) -> CustomResult { +) -> CustomResult { let delete_card_req = GetCard { merchant_id, card_id, }; let body = utils::Encode::>::encode(&delete_card_req) - .change_context(errors::CardVaultError::RequestEncodingFailed)?; + .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/deleteCard"); let mut request = services::Request::new(services::Method::Post, &url); @@ -174,11 +174,11 @@ pub fn mk_delete_card_request<'a>( pub fn get_card_detail( pm: &storage::PaymentMethod, response: AddCardResponse, -) -> CustomResult { +) -> CustomResult { let card_number = response .card_number .get_required_value("card_number") - .change_context(errors::CardVaultError::FetchCardFailed)?; + .change_context(errors::VaultError::FetchCardFailed)?; let mut last4_digits = card_number.peek().to_owned(); let card_detail = api::CardDetailFromLocker { scheme: pm.scheme.clone(), @@ -199,9 +199,9 @@ pub fn mk_crud_locker_request( locker: &Locker, path: &str, req: api::TokenizePayloadEncrypted, -) -> CustomResult { +) -> CustomResult { let body = utils::Encode::::encode_to_value(&req) - .change_context(errors::CardVaultError::RequestEncodingFailed)?; + .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.basilisk_host.to_owned(); url.push_str(path); let mut request = services::Request::new(services::Method::Post, &url); @@ -219,7 +219,7 @@ pub fn mk_card_value1( nickname: Option, card_last_four: Option, card_token: Option, -) -> CustomResult { +) -> CustomResult { let value1 = api::TokenizedCardValue1 { card_number, exp_year, @@ -230,7 +230,7 @@ pub fn mk_card_value1( card_token, }; let value1_req = utils::Encode::::encode_to_string_of_json(&value1) - .change_context(errors::CardVaultError::FetchCardFailed)?; + .change_context(errors::VaultError::FetchCardFailed)?; Ok(value1_req) } @@ -240,7 +240,7 @@ pub fn mk_card_value2( external_id: Option, customer_id: Option, payment_method_id: Option, -) -> CustomResult { +) -> CustomResult { let value2 = api::TokenizedCardValue2 { card_security_code, card_fingerprint, @@ -249,6 +249,6 @@ pub fn mk_card_value2( payment_method_id, }; let value2_req = utils::Encode::::encode_to_string_of_json(&value2) - .change_context(errors::CardVaultError::FetchCardFailed)?; + .change_context(errors::VaultError::FetchCardFailed)?; Ok(value2_req) } diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs new file mode 100644 index 0000000000..4868410613 --- /dev/null +++ b/crates/router/src/core/payment_methods/vault.rs @@ -0,0 +1,543 @@ +use std::str::FromStr; + +use common_utils::generate_id_with_default_len; +use error_stack::{IntoReport, ResultExt}; +use masking::PeekInterface; +use router_env::{instrument, tracing}; + +#[cfg(not(feature = "basilisk"))] +use crate::types::storage; +use crate::{ + core::{ + errors::{self, CustomResult, RouterResult}, + payment_methods::transformers as payment_methods, + }, + logger, routes, services, + types::api, + utils::{self, BytesExt, StringExt}, +}; + +const VAULT_SERVICE_NAME: &str = "CARD"; +const VAULT_VERSION: &str = "0"; + +pub struct SupplementaryVaultData { + pub customer_id: Option, + pub payment_method_id: Option, +} + +pub trait Vaultable: Sized { + fn get_value1(&self, customer_id: Option) -> CustomResult; + fn get_value2(&self, _customer_id: Option) -> CustomResult { + Ok(String::new()) + } + fn from_values( + value1: String, + value2: String, + ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError>; +} + +impl Vaultable for api::CCard { + fn get_value1(&self, _customer_id: Option) -> CustomResult { + let value1 = api::TokenizedCardValue1 { + card_number: self.card_number.peek().clone(), + exp_year: self.card_exp_year.peek().clone(), + exp_month: self.card_exp_month.peek().clone(), + name_on_card: Some(self.card_holder_name.peek().clone()), + nickname: None, + card_last_four: None, + card_token: None, + }; + + utils::Encode::::encode_to_string_of_json(&value1) + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode card value1") + } + + fn get_value2(&self, customer_id: Option) -> CustomResult { + let value2 = api::TokenizedCardValue2 { + card_security_code: Some(self.card_cvc.peek().clone()), + card_fingerprint: None, + external_id: None, + customer_id, + payment_method_id: None, + }; + + utils::Encode::::encode_to_string_of_json(&value2) + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode card value2") + } + + fn from_values( + value1: String, + value2: String, + ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> { + let value1: api::TokenizedCardValue1 = value1 + .parse_struct("TokenizedCardValue1") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Could not deserialize into card value1")?; + + let value2: api::TokenizedCardValue2 = value2 + .parse_struct("TokenizedCardValue2") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Could not deserialize into card value2")?; + + let card = Self { + card_number: value1.card_number.into(), + card_exp_month: value1.exp_month.into(), + card_exp_year: value1.exp_year.into(), + card_holder_name: value1.name_on_card.unwrap_or_default().into(), + card_cvc: value2.card_security_code.unwrap_or_default().into(), + }; + + let supp_data = SupplementaryVaultData { + customer_id: value2.customer_id, + payment_method_id: None, + }; + + Ok((card, supp_data)) + } +} + +impl Vaultable for api::WalletData { + fn get_value1(&self, _customer_id: Option) -> CustomResult { + let value1 = api::TokenizedWalletValue1 { + issuer: self.issuer_name.to_string(), + token: self.token.clone(), + }; + + utils::Encode::::encode_to_string_of_json(&value1) + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode wallet data value1") + } + + fn get_value2(&self, customer_id: Option) -> CustomResult { + let value2 = api::TokenizedWalletValue2 { customer_id }; + + utils::Encode::::encode_to_string_of_json(&value2) + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode wallet data value2") + } + + fn from_values( + value1: String, + value2: String, + ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> { + let value1: api::TokenizedWalletValue1 = value1 + .parse_struct("TokenizedWalletValue1") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Could not deserialize into wallet data value1")?; + + let value2: api::TokenizedWalletValue2 = value2 + .parse_struct("TokenizedWalletValue2") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Could not deserialize into wallet data value2")?; + + let wallet = Self { + issuer_name: api::enums::WalletIssuer::from_str(&value1.issuer) + .into_report() + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Invalid issuer name when deserializing wallet data")?, + token: value1.token, + }; + + let supp_data = SupplementaryVaultData { + customer_id: value2.customer_id, + payment_method_id: None, + }; + + Ok((wallet, supp_data)) + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(tag = "type", content = "value", rename_all = "snake_case")] +pub enum VaultPaymentMethod { + Card(String), + Wallet(String), +} + +impl Vaultable for api::PaymentMethod { + fn get_value1(&self, customer_id: Option) -> CustomResult { + let value1 = match self { + Self::Card(card) => VaultPaymentMethod::Card(card.get_value1(customer_id)?), + Self::Wallet(wallet) => VaultPaymentMethod::Wallet(wallet.get_value1(customer_id)?), + _ => Err(errors::VaultError::PaymentMethodNotSupported) + .into_report() + .attach_printable("Payment method not supported")?, + }; + + utils::Encode::::encode_to_string_of_json(&value1) + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode payment method value1") + } + + fn get_value2(&self, customer_id: Option) -> CustomResult { + let value2 = match self { + Self::Card(card) => VaultPaymentMethod::Card(card.get_value2(customer_id)?), + Self::Wallet(wallet) => VaultPaymentMethod::Wallet(wallet.get_value2(customer_id)?), + _ => Err(errors::VaultError::PaymentMethodNotSupported) + .into_report() + .attach_printable("Payment method not supported")?, + }; + + utils::Encode::::encode_to_string_of_json(&value2) + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode payment method value2") + } + + fn from_values( + value1: String, + value2: String, + ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> { + let value1: VaultPaymentMethod = value1 + .parse_struct("PaymentMethodValue1") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Could not deserialize into payment method value 1")?; + + let value2: VaultPaymentMethod = value2 + .parse_struct("PaymentMethodValue2") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Could not deserialize into payment method value 2")?; + + match (value1, value2) { + (VaultPaymentMethod::Card(mvalue1), VaultPaymentMethod::Card(mvalue2)) => { + let (card, supp_data) = api::CCard::from_values(mvalue1, mvalue2)?; + Ok((Self::Card(card), supp_data)) + } + (VaultPaymentMethod::Wallet(mvalue1), VaultPaymentMethod::Wallet(mvalue2)) => { + let (wallet, supp_data) = api::WalletData::from_values(mvalue1, mvalue2)?; + Ok((Self::Wallet(wallet), supp_data)) + } + _ => Err(errors::VaultError::PaymentMethodNotSupported) + .into_report() + .attach_printable("Payment method not supported"), + } + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct MockTokenizeDBValue { + pub value1: String, + pub value2: String, +} + +pub struct Vault; + +#[cfg(not(feature = "basilisk"))] +impl Vault { + #[instrument(skip_all)] + pub async fn get_payment_method_data_from_locker( + state: &routes::AppState, + lookup_key: &str, + ) -> RouterResult<(Option, SupplementaryVaultData)> { + let config = state + .store + .find_config_by_key(lookup_key) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not find payment method in vault")?; + + let tokenize_value: MockTokenizeDBValue = config + .config + .parse_struct("MockTokenizeDBValue") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to deserialize Mock tokenize db value")?; + + let (payment_method, supp_data) = + api::PaymentMethod::from_values(tokenize_value.value1, tokenize_value.value2) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error parsing Payment Method from Values")?; + + Ok((Some(payment_method), supp_data)) + } + + #[instrument(skip_all)] + pub async fn store_payment_method_data_in_locker( + state: &routes::AppState, + token_id: Option, + payment_method: &api::PaymentMethod, + customer_id: Option, + ) -> RouterResult { + let value1 = payment_method + .get_value1(customer_id.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting Value1 for locker")?; + + let value2 = payment_method + .get_value2(customer_id) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting Value12 for locker")?; + + let lookup_key = token_id.unwrap_or_else(|| generate_id_with_default_len("token")); + + let db_value = MockTokenizeDBValue { value1, value2 }; + + let value_string = + utils::Encode::::encode_to_string_of_json(&db_value) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encode payment method as mock tokenize db value")?; + + let config = storage::ConfigNew { + key: lookup_key.clone(), + config: value_string, + }; + + state + .store + .insert_config(config) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Mock tokenization save to db failed")?; + + Ok(lookup_key) + } + + #[instrument(skip_all)] + pub async fn delete_locker_payment_method_by_lookup_key( + state: &routes::AppState, + lookup_key: &Option, + ) { + let db = &*state.store; + if let Some(id) = lookup_key { + match db.delete_config_by_key(id).await { + Ok(_) => logger::info!("Card Deleted from locker mock up"), + Err(err) => logger::error!("Err: Card Delete from locker Failed : {}", err), + } + } + } +} + +#[cfg(feature = "basilisk")] +impl Vault { + #[instrument(skip_all)] + pub async fn get_payment_method_data_from_locker( + state: &routes::AppState, + lookup_key: &str, + ) -> RouterResult<(Option, SupplementaryVaultData)> { + let de_tokenize = get_tokenized_data(state, lookup_key, true).await?; + let (payment_method, customer_id) = + api::PaymentMethod::from_values(de_tokenize.value1, de_tokenize.value2) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error parsing Payment Method from Values")?; + + Ok((Some(payment_method), customer_id)) + } + + #[instrument(skip_all)] + pub async fn store_payment_method_data_in_locker( + state: &routes::AppState, + token_id: Option, + payment_method: &api::PaymentMethod, + customer_id: Option, + ) -> RouterResult { + let value1 = payment_method + .get_value1(customer_id.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting Value1 for locker")?; + + let value2 = payment_method + .get_value2(customer_id) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting Value12 for locker")?; + + let lookup_key = token_id.unwrap_or_else(|| generate_id_with_default_len("token")); + + create_tokenize(state, value1, Some(value2), lookup_key).await + } + + #[instrument(skip_all)] + pub async fn delete_locker_payment_method_by_lookup_key( + state: &routes::AppState, + lookup_key: &Option, + ) { + if let Some(lookup_key) = lookup_key { + let delete_resp = delete_tokenized_data(state, lookup_key).await; + match delete_resp { + Ok(resp) => { + if resp == "Ok" { + logger::info!("Card From locker deleted Successfully") + } else { + logger::error!("Error: Deleting Card From Locker : {}", resp) + } + } + Err(err) => logger::error!("Err: Deleting Card From Locker : {}", err), + } + } + } +} + +//------------------------------------------------TokenizeService------------------------------------------------ +pub async fn create_tokenize( + state: &routes::AppState, + value1: String, + value2: Option, + lookup_key: String, +) -> RouterResult { + let payload_to_be_encrypted = api::TokenizePayloadRequest { + value1, + value2: value2.unwrap_or_default(), + lookup_key, + service_name: VAULT_SERVICE_NAME.to_string(), + }; + let payload = utils::Encode::::encode_to_string_of_json( + &payload_to_be_encrypted, + ) + .change_context(errors::ApiErrorResponse::InternalServerError)?; + let encrypted_payload = services::encrypt_jwe(&state.conf.jwekey, &payload) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting Encrypt JWE response")?; + + let create_tokenize_request = api::TokenizePayloadEncrypted { + payload: encrypted_payload, + key_id: services::get_key_id(&state.conf.jwekey).to_string(), + version: Some(VAULT_VERSION.to_string()), + }; + let request = payment_methods::mk_crud_locker_request( + &state.conf.locker, + "/tokenize", + create_tokenize_request, + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Making tokenize request failed")?; + let response = services::call_connector_api(state, request) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + match response { + Ok(r) => { + let resp: api::TokenizePayloadEncrypted = r + .response + .parse_struct("TokenizePayloadEncrypted") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Decoding Failed for TokenizePayloadEncrypted")?; + let decrypted_payload = + services::decrypt_jwe(&state.conf.jwekey, &resp.payload, &resp.key_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Decrypt Jwe failed for TokenizePayloadEncrypted")?; + let get_response: api::GetTokenizePayloadResponse = decrypted_payload + .parse_struct("GetTokenizePayloadResponse") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Error getting GetTokenizePayloadResponse from tokenize response", + )?; + Ok(get_response.lookup_key) + } + Err(err) => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable(format!("Got 4xx from the basilisk locker: {err:?}")), + } +} + +pub async fn get_tokenized_data( + state: &routes::AppState, + lookup_key: &str, + should_get_value2: bool, +) -> RouterResult { + let payload_to_be_encrypted = api::GetTokenizePayloadRequest { + lookup_key: lookup_key.to_string(), + get_value2: should_get_value2, + }; + let payload = serde_json::to_string(&payload_to_be_encrypted) + .map_err(|_x| errors::ApiErrorResponse::InternalServerError)?; + let encrypted_payload = services::encrypt_jwe(&state.conf.jwekey, &payload) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting Encrypt JWE response")?; + let create_tokenize_request = api::TokenizePayloadEncrypted { + payload: encrypted_payload, + key_id: services::get_key_id(&state.conf.jwekey).to_string(), + version: Some("0".to_string()), + }; + let request = payment_methods::mk_crud_locker_request( + &state.conf.locker, + "/tokenize/get", + create_tokenize_request, + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Making Get Tokenized request failed")?; + let response = services::call_connector_api(state, request) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + match response { + Ok(r) => { + let resp: api::TokenizePayloadEncrypted = r + .response + .parse_struct("TokenizePayloadEncrypted") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Decoding Failed for TokenizePayloadEncrypted")?; + let decrypted_payload = + services::decrypt_jwe(&state.conf.jwekey, &resp.payload, &resp.key_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "GetTokenizedApi: Decrypt Jwe failed for TokenizePayloadEncrypted", + )?; + let get_response: api::TokenizePayloadRequest = decrypted_payload + .parse_struct("TokenizePayloadRequest") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting TokenizePayloadRequest from tokenize response")?; + Ok(get_response) + } + Err(err) => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable(format!("Got 4xx from the basilisk locker: {err:?}")), + } +} + +pub async fn delete_tokenized_data( + state: &routes::AppState, + lookup_key: &str, +) -> RouterResult { + let payload_to_be_encrypted = api::DeleteTokenizeByTokenRequest { + lookup_key: lookup_key.to_string(), + }; + let payload = serde_json::to_string(&payload_to_be_encrypted) + .map_err(|_x| errors::ApiErrorResponse::InternalServerError)?; + let encrypted_payload = services::encrypt_jwe(&state.conf.jwekey, &payload) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting Encrypt JWE response")?; + let create_tokenize_request = api::TokenizePayloadEncrypted { + payload: encrypted_payload, + key_id: services::get_key_id(&state.conf.jwekey).to_string(), + version: Some("0".to_string()), + }; + let request = payment_methods::mk_crud_locker_request( + &state.conf.locker, + "/tokenize/delete/token", + create_tokenize_request, + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Making Delete Tokenized request failed")?; + let response = services::call_connector_api(state, request) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + match response { + Ok(r) => { + let resp: api::TokenizePayloadEncrypted = r + .response + .parse_struct("TokenizePayloadEncrypted") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Decoding Failed for TokenizePayloadEncrypted")?; + let decrypted_payload = + services::decrypt_jwe(&state.conf.jwekey, &resp.payload, &resp.key_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "DeleteTokenizedApi: Decrypt Jwe failed for TokenizePayloadEncrypted", + )?; + let delete_response = decrypted_payload + .parse_struct("Delete") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Error getting TokenizePayloadEncrypted from tokenize response", + )?; + Ok(delete_response) + } + Err(err) => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable(format!("Got 4xx from the basilisk locker: {err:?}")), + } +} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c4ee5b7bb8..b8ddbf3082 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -20,7 +20,10 @@ use self::{ operations::{BoxedOperation, Operation}, }; use crate::{ - core::errors::{self, RouterResponse, RouterResult}, + core::{ + errors::{self, RouterResponse, RouterResult}, + payment_methods::vault, + }, db::StorageInterface, logger, pii, routes::AppState, @@ -147,7 +150,7 @@ where .await? } }; - helpers::Vault::delete_locker_payment_method_by_lookup_key(state, &payment_data.token).await + vault::Vault::delete_locker_payment_method_by_lookup_key(state, &payment_data.token).await } Ok((payment_data, req, customer)) } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index f3bc569f7f..ebfd1c9666 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use common_utils::ext_traits::AsyncExt; // TODO : Evaluate all the helper functions () use error_stack::{report, IntoReport, ResultExt}; -use masking::{ExposeOptionInterface, PeekInterface}; +use masking::ExposeOptionInterface; use router_env::{instrument, tracing}; use uuid::Uuid; @@ -16,10 +16,9 @@ use crate::{ consts, core::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, - payment_methods::cards, + payment_methods::{cards, vault}, }, db::StorageInterface, - logger, routes::AppState, scheduler::{metrics, workflows::payment_sync}, services, @@ -709,7 +708,6 @@ pub async fn make_pm_data<'a, F: Clone, R>( payment_data: &mut PaymentData, ) -> RouterResult<(BoxedOperation<'a, F, R>, Option)> { let payment_method_type = payment_data.payment_attempt.payment_method; - let attempt_id = &payment_data.payment_attempt.attempt_id; let request = &payment_data.payment_method_data; let token = payment_data.token.clone(); let card_cvc = payment_data.card_cvc.clone(); @@ -717,11 +715,11 @@ pub async fn make_pm_data<'a, F: Clone, R>( let payment_method = match (request, token) { (_, Some(token)) => Ok::<_, error_stack::Report>( if payment_method_type == Some(storage_enums::PaymentMethodType::Card) { - // [#196]: Handle token expiry - let (pm, tokenize_value2) = - Vault::get_payment_method_data_from_locker(state, &token).await?; + // TODO: Handle token expiry + let (pm, supplementary_data) = + vault::Vault::get_payment_method_data_from_locker(state, &token).await?; utils::when( - tokenize_value2 + supplementary_data .customer_id .ne(&payment_data.payment_intent.customer_id), || { @@ -733,14 +731,15 @@ pub async fn make_pm_data<'a, F: Clone, R>( (Some(api::PaymentMethod::Card(card)), Some(card_cvc)) => { let mut updated_card = card; updated_card.card_cvc = card_cvc; - Vault::store_payment_method_data_in_locker( + let updated_pm = api::PaymentMethod::Card(updated_card); + vault::Vault::store_payment_method_data_in_locker( state, - &token, - &updated_card, + Some(token), + &updated_pm, payment_data.payment_intent.customer_id.to_owned(), ) .await?; - Some(api::PaymentMethod::Card(updated_card)) + Some(updated_pm) } (_, _) => pm, } @@ -754,194 +753,35 @@ pub async fn make_pm_data<'a, F: Clone, R>( None }, ), - (pm @ Some(api::PaymentMethod::Card(card)), _) => { - Vault::store_payment_method_data_in_locker( + (pm_opt @ Some(pm @ api::PaymentMethod::Card(_)), _) => { + let token = vault::Vault::store_payment_method_data_in_locker( state, - attempt_id, - card, + None, + pm, payment_data.payment_intent.customer_id.to_owned(), ) .await?; - payment_data.token = Some(attempt_id.to_string()); - Ok(pm.to_owned()) + payment_data.token = Some(token); + Ok(pm_opt.to_owned()) } (pm @ Some(api::PaymentMethod::PayLater(_)), _) => Ok(pm.to_owned()), - (pm @ Some(api::PaymentMethod::Wallet(_)), _) => Ok(pm.to_owned()), + (pm_opt @ Some(pm @ api::PaymentMethod::Wallet(_)), _) => { + let token = vault::Vault::store_payment_method_data_in_locker( + state, + None, + pm, + payment_data.payment_intent.customer_id.to_owned(), + ) + .await?; + payment_data.token = Some(token); + Ok(pm_opt.to_owned()) + } _ => Ok(None), }?; Ok((operation, payment_method)) } -pub struct Vault {} - -#[cfg(not(feature = "basilisk"))] -impl Vault { - #[instrument(skip_all)] - pub async fn get_payment_method_data_from_locker( - state: &AppState, - lookup_key: &str, - ) -> RouterResult<(Option, api::TokenizedCardValue2)> { - let (resp, card_cvc) = cards::mock_get_card(&*state.store, lookup_key) - .await - .change_context(errors::ApiErrorResponse::InternalServerError)?; - let card = resp.card; - let card_number = card - .card_number - .expose_option() - .get_required_value("card_number")?; - let card_exp_month = card - .card_exp_month - .expose_option() - .get_required_value("expiry_month")?; - let card_exp_year = card - .card_exp_year - .expose_option() - .get_required_value("expiry_year")?; - let card_holder_name = card.name_on_card.expose_option().unwrap_or_default(); - let pm = api::PaymentMethod::Card(api::CCard { - card_number: card_number.into(), - card_exp_month: card_exp_month.into(), - card_exp_year: card_exp_year.into(), - card_holder_name: card_holder_name.into(), - card_cvc: card_cvc.unwrap_or_default().into(), - }); - let value2 = api::TokenizedCardValue2 { - card_security_code: None, - card_fingerprint: None, - external_id: None, - customer_id: card.customer_id, - payment_method_id: Some(card.card_id), - }; - Ok((Some(pm), value2)) - } - - #[instrument(skip_all)] - async fn store_payment_method_data_in_locker( - state: &AppState, - txn_id: &str, - card: &api::CCard, - customer_id: Option, - ) -> RouterResult { - let card_detail = api::CardDetail { - card_number: card.card_number.clone(), - card_exp_month: card.card_exp_month.clone(), - card_exp_year: card.card_exp_year.clone(), - card_holder_name: Some(card.card_holder_name.clone()), - }; - let db = &*state.store; - cards::mock_add_card( - db, - txn_id, - &card_detail, - Some(card.card_cvc.peek().clone()), - None, - customer_id.as_deref(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Add Card Failed")?; - Ok(txn_id.to_string()) - } - - #[instrument(skip_all)] - pub async fn delete_locker_payment_method_by_lookup_key( - state: &AppState, - lookup_key: &Option, - ) { - let db = &*state.store; - if let Some(id) = lookup_key { - match cards::mock_delete_card(db, id).await { - Ok(_) => logger::info!("Card Deleted from locker mock up"), - Err(err) => logger::error!("Err: Card Delete from locker Failed : {}", err), - } - } - } -} - -#[cfg(feature = "basilisk")] -use crate::{core::payment_methods::transformers, utils::StringExt}; - -#[cfg(feature = "basilisk")] -impl Vault { - #[instrument(skip_all)] - pub async fn get_payment_method_data_from_locker( - state: &AppState, - lookup_key: &str, - ) -> RouterResult<(Option, api::TokenizedCardValue2)> { - let de_tokenize = cards::get_tokenized_data(state, lookup_key, true).await?; - let value1: api::TokenizedCardValue1 = de_tokenize - .value1 - .parse_struct("TokenizedCardValue1") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error parsing TokenizedCardValue1")?; - let value2: api::TokenizedCardValue2 = de_tokenize - .value2 - .parse_struct("TokenizedCardValue2") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error parsing TokenizedCardValue2")?; - - let card = api::PaymentMethod::Card(api::CCard { - card_number: value1.card_number.into(), - card_exp_month: value1.exp_month.into(), - card_exp_year: value1.exp_year.into(), - card_holder_name: value1.name_on_card.unwrap_or_default().into(), - card_cvc: value2.card_security_code.clone().unwrap_or_default().into(), - }); - Ok((Some(card), value2)) - } - - #[instrument(skip_all)] - async fn store_payment_method_data_in_locker( - state: &AppState, - txn_id: &str, - card: &api::CCard, - customer_id: Option, - ) -> RouterResult { - let value1 = transformers::mk_card_value1( - card.card_number.peek().clone(), - card.card_exp_year.peek().clone(), - card.card_exp_month.peek().clone(), - Some(card.card_holder_name.peek().clone()), - None, - None, - None, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Value1 for locker")?; - let value2 = transformers::mk_card_value2( - Some(card.card_cvc.peek().clone()), - None, - None, - customer_id, - None, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Value12 for locker")?; - cards::create_tokenize(state, value1, Some(value2), txn_id.to_string()).await - } - - #[instrument(skip_all)] - pub async fn delete_locker_payment_method_by_lookup_key( - state: &AppState, - lookup_key: &Option, - ) { - if let Some(lookup_key) = lookup_key { - let delete_resp = cards::delete_tokenized_data(state, lookup_key).await; - match delete_resp { - Ok(resp) => { - if resp == "Ok" { - logger::info!("Card From locker deleted Successfully") - } else { - logger::error!("Error: Deleting Card From Locker : {}", resp) - } - } - Err(err) => logger::error!("Err: Deleting Card From Locker : {}", err), - } - } - } -} - #[instrument(skip_all)] pub(crate) fn validate_capture_method( capture_method: storage_enums::CaptureMethod, diff --git a/crates/router/src/db/configs.rs b/crates/router/src/db/configs.rs index 7b8a1fc10b..97af1ab152 100644 --- a/crates/router/src/db/configs.rs +++ b/crates/router/src/db/configs.rs @@ -13,15 +13,19 @@ pub trait ConfigInterface { &self, config: storage::ConfigNew, ) -> CustomResult; + async fn find_config_by_key( &self, key: &str, ) -> CustomResult; + async fn update_config_by_key( &self, key: &str, config_update: storage::ConfigUpdate, ) -> CustomResult; + + async fn delete_config_by_key(&self, key: &str) -> CustomResult; } #[async_trait::async_trait] @@ -56,6 +60,14 @@ impl ConfigInterface for Store { .map_err(Into::into) .into_report() } + + async fn delete_config_by_key(&self, key: &str) -> CustomResult { + let conn = pg_connection(&self.master_pool).await; + storage::Config::delete_by_key(&conn, key) + .await + .map_err(Into::into) + .into_report() + } } #[async_trait::async_trait] @@ -84,4 +96,9 @@ impl ConfigInterface for MockDb { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } + + async fn delete_config_by_key(&self, _key: &str) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(errors::StorageError::MockDbError)? + } } diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index fb068b8a04..4f2f532411 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -6,7 +6,7 @@ pub use api_models::payment_methods::{ GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCustomerPaymentMethodsResponse, ListPaymentMethodRequest, ListPaymentMethodResponse, PaymentMethodId, PaymentMethodResponse, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, - UpdatePaymentMethod, + TokenizedWalletValue1, TokenizedWalletValue2, UpdatePaymentMethod, }; use error_stack::report; use literally::hmap; diff --git a/crates/storage_models/src/query/configs.rs b/crates/storage_models/src/query/configs.rs index 705a6f1a7b..62ed60e3b0 100644 --- a/crates/storage_models/src/query/configs.rs +++ b/crates/storage_models/src/query/configs.rs @@ -1,10 +1,12 @@ -use diesel::associations::HasTable; +use diesel::{associations::HasTable, ExpressionMethods}; use router_env::{instrument, tracing}; use super::generics; use crate::{ configs::{Config, ConfigNew, ConfigUpdate, ConfigUpdateInternal}, - errors, PgPooledConn, StorageResult, + errors, + schema::configs::dsl, + PgPooledConn, StorageResult, }; impl ConfigNew { @@ -46,4 +48,10 @@ impl Config { result => result, } } + + #[instrument(skip(conn))] + pub async fn delete_by_key(conn: &PgPooledConn, key: &str) -> StorageResult { + generics::generic_delete::<::Table, _>(conn, dsl::key.eq(key.to_owned())) + .await + } }