feat(payment_methods_v2): Delete payment method api (#6211)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sarthak Soni
2024-10-14 15:42:55 +05:30
committed by GitHub
parent 1ac8c92c4b
commit 8e538cd6b3
8 changed files with 410 additions and 250 deletions

View File

@ -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<storage_enums::PaymentMethodType>,
pub metadata: Option<pii::SecretSerdeValue>,
pub payment_method_data: OptionalEncryptableValue,
pub locker_id: Option<String>,
pub locker_id: Option<VaultId>,
pub last_used_at: PrimitiveDateTime,
pub connector_mandate_details: Option<pii::SecretSerdeValue>,
pub customer_acceptance: Option<pii::SecretSerdeValue>,
@ -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,

View File

@ -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";

View File

@ -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<String>,
locker_id: Option<domain::VaultId>,
merchant_id: &id_type::MerchantId,
pm_metadata: Option<common_utils::pii::SecretSerdeValue>,
customer_acceptance: Option<common_utils::pii::SecretSerdeValue>,
@ -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<String>,
existing_vault_id: Option<domain::VaultId>,
) -> RouterResult<pm_types::AddVaultResponse> {
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<api::PaymentMethodDeleteResponse> {
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(

View File

@ -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<R: pm_types::VaultingInterface>(
jwekey: &settings::Jwekey,
locker: &settings::Locker,
payload: Vec<u8>,
) -> errors::CustomResult<Request, errors::VaultError> {
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<String, errors::VaultError> {
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::<pm_types::GetVaultFingerprint>(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<String>,
) -> errors::CustomResult<pm_types::AddVaultResponse, errors::VaultError> {
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::<pm_types::AddVault>(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<pm_types::VaultRetrieveResponse, errors::VaultError> {
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::<pm_types::VaultRetrieve>(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<V: pm_types::VaultingInterface>(
state: &routes::SessionState,
payload: Vec<u8>,
) -> errors::CustomResult<String, errors::VaultError> {
let locker = &state.conf.locker;
let jwekey = state.conf.jwekey.get_inner();
let request = create_vault_request::<V>(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<T>(
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<api::PaymentMethodDeleteResponse> {
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<api::PaymentMethodDeleteResponse> {
todo!()
}
pub async fn create_encrypted_data<T>(
state: &routes::SessionState,
key_store: &domain::MerchantKeyStore,

View File

@ -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<R: pm_types::VaultingInterface>(
jwekey: &settings::Jwekey,
locker: &settings::Locker,
payload: Vec<u8>,
) -> CustomResult<request::Request, errors::VaultError> {
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<V: pm_types::VaultingInterface>(
state: &routes::SessionState,
payload: Vec<u8>,
) -> CustomResult<String, errors::VaultError> {
let locker = &state.conf.locker;
let jwekey = state.conf.jwekey.get_inner();
let request = create_vault_request::<V>(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<String, errors::VaultError> {
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::<pm_types::GetVaultFingerprint>(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<domain::VaultId>,
) -> CustomResult<pm_types::AddVaultResponse, errors::VaultError> {
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::<pm_types::AddVault>(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<pm_types::VaultRetrieveResponse, errors::VaultError> {
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::<pm_types::VaultRetrieve>(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<pm_types::VaultDeleteResponse, errors::VaultError> {
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::<pm_types::VaultDelete>(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(

View File

@ -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
}

View File

@ -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<AppState>,
req: HttpRequest,
path: web::Path<String>,
) -> 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<AppState>,
@ -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<AppState>,

View File

@ -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<D> {
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<D> {
#[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<String>,
}
@ -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<String>,
}
#[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,
}