mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 20:23:43 +08:00
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:
@ -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,
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user