refactor(payment_methods): unify locker api function call (#5863)

This commit is contained in:
Chethan Rao
2024-09-16 12:19:49 +05:30
committed by GitHub
parent 2aa215440e
commit 4137d7b48a
6 changed files with 181 additions and 88 deletions

View File

@ -4490,7 +4490,7 @@ async fn locker_recipient_create_call(
ttl: state.conf.locker.ttl_for_storage_in_secs,
});
let store_resp = cards::call_to_locker_hs(
let store_resp = cards::add_card_to_hs_locker(
state,
&payload,
&cust_id,

View File

@ -124,6 +124,8 @@ pub enum VaultError {
SaveCardFailed,
#[error("Failed to fetch card details from card vault")]
FetchCardFailed,
#[error("Failed to delete card in card vault")]
DeleteCardFailed,
#[error("Failed to encode card vault request")]
RequestEncodingFailed,
#[error("Failed to deserialize card vault response")]
@ -146,6 +148,8 @@ pub enum VaultError {
SavePaymentMethodFailed,
#[error("Failed to generate fingerprint")]
GenerateFingerprintFailed,
#[error("Failed while calling locker API")]
ApiError,
}
#[derive(Debug, thiserror::Error)]

View File

@ -28,8 +28,10 @@ use common_utils::{
consts,
crypto::{self, Encryptable},
encryption::Encryption,
ext_traits::{AsyncExt, Encode, StringExt, ValueExt},
generate_id, id_type, type_name,
ext_traits::{AsyncExt, BytesExt, Encode, StringExt, ValueExt},
generate_id, id_type,
request::Request,
type_name,
types::{
keymanager::{Identifier, KeyManagerState},
MinorUnit,
@ -93,7 +95,7 @@ use crate::{
storage::{self, enums, PaymentMethodListContext, PaymentTokenData},
transformers::ForeignTryFrom,
},
utils::{ConnectorResponseExt, OptionExt},
utils::OptionExt,
};
#[cfg(all(
any(feature = "v1", feature = "v2"),
@ -1876,7 +1878,7 @@ pub async fn add_bank_to_locker(
enc_data,
ttl: state.conf.locker.ttl_for_storage_in_secs,
});
let store_resp = call_to_locker_hs(
let store_resp = add_card_to_hs_locker(
state,
&payload,
customer_id,
@ -1920,7 +1922,7 @@ pub async fn add_card_to_locker(
card_reference,
)
.await
.inspect_err(|error| {
.inspect_err(|_| {
metrics::CARD_LOCKER_FAILURES.add(
&metrics::CONTEXT,
1,
@ -1929,7 +1931,6 @@ pub async fn add_card_to_locker(
router_env::opentelemetry::KeyValue::new("operation", "add"),
],
);
logger::error!(?error, "Failed to add card in locker");
})
},
&metrics::CARD_ADD_TIME,
@ -1962,7 +1963,7 @@ pub async fn get_card_from_locker(
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while getting card from hyperswitch card vault")
.inspect_err(|error| {
.inspect_err(|_| {
metrics::CARD_LOCKER_FAILURES.add(
&metrics::CONTEXT,
1,
@ -1971,7 +1972,6 @@ pub async fn get_card_from_locker(
router_env::opentelemetry::KeyValue::new("operation", "get"),
],
);
logger::error!(?error, "Failed to retrieve card from locker");
})
},
&metrics::CARD_GET_TIME,
@ -1996,9 +1996,15 @@ pub async fn delete_card_from_locker(
async move {
delete_card_from_hs_locker(state, customer_id, merchant_id, card_reference)
.await
.inspect_err(|error| {
metrics::CARD_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]);
logger::error!(?error, "Failed to delete card from locker");
.inspect_err(|_| {
metrics::CARD_LOCKER_FAILURES.add(
&metrics::CONTEXT,
1,
&[
router_env::opentelemetry::KeyValue::new("locker", "rust"),
router_env::opentelemetry::KeyValue::new("operation", "delete"),
],
);
})
},
&metrics::CARD_DELETE_TIME,
@ -2006,6 +2012,8 @@ pub async fn delete_card_from_locker(
&[],
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while deleting card from locker")
}
#[cfg(all(feature = "v2", feature = "customer_v2"))]
@ -2049,7 +2057,8 @@ pub async fn add_card_hs(
ttl: state.conf.locker.ttl_for_storage_in_secs,
});
let store_card_payload = call_to_locker_hs(state, &payload, customer_id, locker_choice).await?;
let store_card_payload =
add_card_to_hs_locker(state, &payload, customer_id, locker_choice).await?;
let payment_method_resp = payment_methods::mk_add_card_response_hs(
card.clone(),
@ -2111,30 +2120,22 @@ pub async fn get_payment_method_from_hs_locker<'a>(
merchant_id,
payment_method_reference,
locker_choice,
state.tenant.name.clone(),
state.request_id,
)
.await
.change_context(errors::VaultError::FetchPaymentMethodFailed)
.attach_printable("Making get payment method request failed")?;
let response = services::call_connector_api(state, request, "add_card_to_locker")
.await
.change_context(errors::VaultError::FetchPaymentMethodFailed)
.attach_printable("Failed while executing call_connector_api for get_card");
let jwe_body: services::JweBody = response
.get_response_inner("JweBody")
.change_context(errors::VaultError::FetchPaymentMethodFailed)?;
let decrypted_payload = payment_methods::get_decrypted_response_payload(
jwekey,
jwe_body,
let get_card_resp = call_locker_api::<payment_methods::RetrieveCardResp>(
state,
request,
"get_pm_from_locker",
locker_choice,
locker.decryption_scheme.clone(),
)
.await
.change_context(errors::VaultError::FetchPaymentMethodFailed)
.attach_printable("Error getting decrypted response payload for get card")?;
let get_card_resp: payment_methods::RetrieveCardResp = decrypted_payload
.parse_struct("RetrieveCardResp")
.change_context(errors::VaultError::FetchPaymentMethodFailed)
.attach_printable("Failed to parse struct to RetrieveCardResp")?;
.change_context(errors::VaultError::FetchPaymentMethodFailed)?;
let retrieve_card_resp = get_card_resp
.payload
.get_required_value("RetrieveCardRespPayload")
@ -2158,7 +2159,7 @@ pub async fn get_payment_method_from_hs_locker<'a>(
}
#[instrument(skip_all)]
pub async fn call_to_locker_hs(
pub async fn add_card_to_hs_locker(
state: &routes::SessionState,
payload: &payment_methods::StoreLockerReq,
customer_id: &id_type::CustomerId,
@ -2168,30 +2169,23 @@ pub async fn call_to_locker_hs(
let jwekey = state.conf.jwekey.get_inner();
let db = &*state.store;
let stored_card_response = if !locker.mock_locker {
let request =
payment_methods::mk_add_locker_request_hs(jwekey, locker, payload, locker_choice)
.await?;
let response = services::call_connector_api(state, request, "add_card_to_hs_locker")
.await
.change_context(errors::VaultError::SaveCardFailed);
let jwe_body: services::JweBody = response
.get_response_inner("JweBody")
.change_context(errors::VaultError::FetchCardFailed)?;
let decrypted_payload = payment_methods::get_decrypted_response_payload(
let request = payment_methods::mk_add_locker_request_hs(
jwekey,
jwe_body,
locker,
payload,
locker_choice,
state.tenant.name.clone(),
state.request_id,
)
.await?;
call_locker_api::<payment_methods::StoreCardResp>(
state,
request,
"add_card_to_hs_locker",
Some(locker_choice),
locker.decryption_scheme.clone(),
)
.await
.change_context(errors::VaultError::SaveCardFailed)
.attach_printable("Error getting decrypted response payload")?;
let stored_card_resp: payment_methods::StoreCardResp = decrypted_payload
.parse_struct("StoreCardResp")
.change_context(errors::VaultError::ResponseDeserializationFailed)?;
stored_card_resp
.change_context(errors::VaultError::SaveCardFailed)?
} else {
let card_id = generate_id(consts::ID_LENGTH, "card");
mock_call_to_locker_hs(db, &card_id, payload, None, None, Some(customer_id)).await?
@ -2204,6 +2198,61 @@ pub async fn call_to_locker_hs(
Ok(stored_card)
}
#[instrument(skip_all)]
pub async fn call_locker_api<T>(
state: &routes::SessionState,
request: Request,
flow_name: &str,
locker_choice: Option<api_enums::LockerChoice>,
) -> errors::CustomResult<T, errors::VaultError>
where
T: serde::de::DeserializeOwned,
{
let locker = &state.conf.locker;
let jwekey = state.conf.jwekey.get_inner();
let response_type_name = type_name!(T);
let response = services::call_connector_api(state, request, flow_name)
.await
.change_context(errors::VaultError::ApiError)?;
let is_locker_call_succeeded = response.is_ok();
let jwe_body = response
.unwrap_or_else(|err| err)
.response
.parse_struct::<services::JweBody>("JweBody")
.change_context(errors::VaultError::ResponseDeserializationFailed)
.attach_printable("Failed while parsing locker response into JweBody")?;
let decrypted_payload = payment_methods::get_decrypted_response_payload(
jwekey,
jwe_body,
locker_choice,
locker.decryption_scheme.clone(),
)
.await
.change_context(errors::VaultError::ResponseDeserializationFailed)
.attach_printable("Failed while decrypting locker payload response")?;
// Irrespective of locker's response status, payload is JWE + JWS decrypted. But based on locker's status,
// if Ok, deserialize the decrypted payload into given type T
// if Err, raise an error including locker error message too
if is_locker_call_succeeded {
let stored_card_resp: Result<T, error_stack::Report<errors::VaultError>> =
decrypted_payload
.parse_struct(response_type_name)
.change_context(errors::VaultError::ResponseDeserializationFailed)
.attach_printable_lazy(|| {
format!("Failed while parsing locker response into {response_type_name}")
});
stored_card_resp
} else {
Err::<T, error_stack::Report<errors::VaultError>>((errors::VaultError::ApiError).into())
.attach_printable_lazy(|| format!("Locker error response: {decrypted_payload:?}"))
}
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
@ -2322,29 +2371,21 @@ pub async fn get_card_from_hs_locker<'a>(
merchant_id,
card_reference,
Some(locker_choice),
state.tenant.name.clone(),
state.request_id,
)
.await
.change_context(errors::VaultError::FetchCardFailed)
.attach_printable("Making get card request failed")?;
let response = services::call_connector_api(state, request, "get_card_from_locker")
.await
.change_context(errors::VaultError::FetchCardFailed)
.attach_printable("Failed while executing call_connector_api for get_card");
let jwe_body: services::JweBody = response
.get_response_inner("JweBody")
.change_context(errors::VaultError::FetchCardFailed)?;
let decrypted_payload = payment_methods::get_decrypted_response_payload(
jwekey,
jwe_body,
let get_card_resp = call_locker_api::<payment_methods::RetrieveCardResp>(
state,
request,
"get_card_from_locker",
Some(locker_choice),
locker.decryption_scheme.clone(),
)
.await
.change_context(errors::VaultError::FetchCardFailed)
.attach_printable("Error getting decrypted response payload for get card")?;
let get_card_resp: payment_methods::RetrieveCardResp = decrypted_payload
.parse_struct("RetrieveCardResp")
.change_context(errors::VaultError::FetchCardFailed)?;
.change_context(errors::VaultError::FetchCardFailed)?;
let retrieve_card_resp = get_card_resp
.payload
.get_required_value("RetrieveCardRespPayload")
@ -2366,7 +2407,7 @@ pub async fn delete_card_from_hs_locker<'a>(
customer_id: &id_type::CustomerId,
merchant_id: &id_type::MerchantId,
card_reference: &'a str,
) -> errors::RouterResult<payment_methods::DeleteCardResp> {
) -> errors::CustomResult<payment_methods::DeleteCardResp, errors::VaultError> {
let locker = &state.conf.locker;
let jwekey = &state.conf.jwekey.get_inner();
@ -2376,35 +2417,26 @@ pub async fn delete_card_from_hs_locker<'a>(
customer_id,
merchant_id,
card_reference,
state.tenant.name.clone(),
state.request_id,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.change_context(errors::VaultError::DeleteCardFailed)
.attach_printable("Making delete card request failed")?;
if !locker.mock_locker {
let response = services::call_connector_api(state, request, "delete_card_from_locker")
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while executing call_connector_api for delete card");
let jwe_body: services::JweBody = response.get_response_inner("JweBody")?;
let decrypted_payload = payment_methods::get_decrypted_response_payload(
jwekey,
jwe_body,
call_locker_api::<payment_methods::DeleteCardResp>(
state,
request,
"delete_card_from_locker",
Some(api_enums::LockerChoice::HyperswitchCardVault),
locker.decryption_scheme.clone(),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting decrypted response payload for delete card")?;
let delete_card_resp: payment_methods::DeleteCardResp = decrypted_payload
.parse_struct("DeleteCardResp")
.change_context(errors::ApiErrorResponse::InternalServerError)?;
Ok(delete_card_resp)
.change_context(errors::VaultError::DeleteCardFailed)
} else {
Ok(mock_delete_card_hs(&*state.store, card_reference)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("card_delete_failure_message")?)
.change_context(errors::VaultError::DeleteCardFailed)?)
}
}
@ -4871,6 +4903,10 @@ pub async fn list_customer_payment_method(
let mut filtered_saved_payment_methods_ctx = Vec::new();
for pm in saved_payment_methods.into_iter() {
logger::debug!(
"Fetching payment method from locker for payment_method_id: {}",
pm.id
);
let payment_method = pm.payment_method.get_required_value("payment_method")?;
let parent_payment_method_token =
is_payment_associated.then(|| generate_id(consts::ID_LENGTH, "token"));

View File

@ -10,6 +10,7 @@ use common_utils::{
};
use error_stack::ResultExt;
use josekit::jwe;
use router_env::tracing_actix_web::RequestId;
use serde::{Deserialize, Serialize};
use crate::{
@ -301,6 +302,8 @@ pub async fn mk_add_locker_request_hs(
locker: &settings::Locker,
payload: &StoreLockerReq,
locker_choice: api_enums::LockerChoice,
tenant_id: String,
request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
let payload = payload
.encode_to_vec()
@ -319,6 +322,13 @@ pub async fn mk_add_locker_request_hs(
url.push_str("/cards/add");
let mut request = services::Request::new(services::Method::Post, &url);
request.add_header(headers::CONTENT_TYPE, "application/json".into());
request.add_header(headers::X_TENANT_ID, tenant_id.into());
if let Some(req_id) = request_id {
request.add_header(
headers::X_REQUEST_ID,
req_id.as_hyphenated().to_string().into(),
);
}
request.set_body(RequestContent::Json(Box::new(jwe_payload)));
Ok(request)
}
@ -425,6 +435,7 @@ pub fn mk_add_card_response_hs(
todo!()
}
#[allow(clippy::too_many_arguments)]
pub async fn mk_get_card_request_hs(
jwekey: &settings::Jwekey,
locker: &settings::Locker,
@ -432,6 +443,8 @@ pub async fn mk_get_card_request_hs(
merchant_id: &id_type::MerchantId,
card_reference: &str,
locker_choice: Option<api_enums::LockerChoice>,
tenant_id: String,
request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
let merchant_customer_id = customer_id.to_owned();
let card_req_body = CardReqBody {
@ -458,6 +471,14 @@ pub async fn mk_get_card_request_hs(
url.push_str("/cards/retrieve");
let mut request = services::Request::new(services::Method::Post, &url);
request.add_header(headers::CONTENT_TYPE, "application/json".into());
request.add_header(headers::X_TENANT_ID, tenant_id.into());
if let Some(req_id) = request_id {
request.add_header(
headers::X_REQUEST_ID,
req_id.as_hyphenated().to_string().into(),
);
}
request.set_body(RequestContent::Json(Box::new(jwe_payload)));
Ok(request)
}
@ -503,6 +524,8 @@ pub async fn mk_delete_card_request_hs(
customer_id: &id_type::CustomerId,
merchant_id: &id_type::MerchantId,
card_reference: &str,
tenant_id: String,
request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
let merchant_customer_id = customer_id.to_owned();
let card_req_body = CardReqBody {
@ -527,6 +550,14 @@ pub async fn mk_delete_card_request_hs(
url.push_str("/cards/delete");
let mut request = services::Request::new(services::Method::Post, &url);
request.add_header(headers::CONTENT_TYPE, "application/json".into());
request.add_header(headers::X_TENANT_ID, tenant_id.into());
if let Some(req_id) = request_id {
request.add_header(
headers::X_REQUEST_ID,
req_id.as_hyphenated().to_string().into(),
);
}
request.set_body(RequestContent::Json(Box::new(jwe_payload)));
Ok(request)
}
@ -539,6 +570,8 @@ pub async fn mk_delete_card_request_hs_by_id(
id: &String,
merchant_id: &id_type::MerchantId,
card_reference: &str,
tenant_id: String,
request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
let merchant_customer_id = id.to_owned();
let card_req_body = CardReqBodyV2 {
@ -563,6 +596,14 @@ pub async fn mk_delete_card_request_hs_by_id(
url.push_str("/cards/delete");
let mut request = services::Request::new(services::Method::Post, &url);
request.add_header(headers::CONTENT_TYPE, "application/json".into());
request.add_header(headers::X_TENANT_ID, tenant_id.into());
if let Some(req_id) = request_id {
request.add_header(
headers::X_REQUEST_ID,
req_id.as_hyphenated().to_string().into(),
);
}
request.set_body(RequestContent::Json(Box::new(jwe_payload)));
Ok(request)
}
@ -641,12 +682,22 @@ pub fn mk_crud_locker_request(
locker: &settings::Locker,
path: &str,
req: api::TokenizePayloadEncrypted,
tenant_id: String,
request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
let mut url = locker.basilisk_host.to_owned();
url.push_str(path);
let mut request = services::Request::new(services::Method::Post, &url);
request.add_default_headers();
request.add_header(headers::CONTENT_TYPE, "application/json".into());
request.add_header(headers::X_TENANT_ID, tenant_id.into());
if let Some(req_id) = request_id {
request.add_header(
headers::X_REQUEST_ID,
req_id.as_hyphenated().to_string().into(),
);
}
request.set_body(RequestContent::Json(Box::new(req)));
Ok(request)
}

View File

@ -307,7 +307,7 @@ pub async fn save_payout_data_to_locker(
};
// Store payout method in locker
let stored_resp = cards::call_to_locker_hs(
let stored_resp = cards::add_card_to_hs_locker(
state,
&locker_req,
customer_id,
@ -559,6 +559,7 @@ pub async fn save_payout_data_to_locker(
card_reference,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Failed to delete PMD from locker as a part of metadata update operation",
)?;
@ -566,7 +567,7 @@ pub async fn save_payout_data_to_locker(
locker_req.update_requestor_card_reference(Some(card_reference.to_string()));
// Store in locker
let stored_resp = cards::call_to_locker_hs(
let stored_resp = cards::add_card_to_hs_locker(
state,
&locker_req,
customer_id,

View File

@ -83,6 +83,7 @@ pub mod headers {
pub const BROWSER_NAME: &str = "x-browser-name";
pub const X_CLIENT_PLATFORM: &str = "x-client-platform";
pub const X_MERCHANT_DOMAIN: &str = "x-merchant-domain";
pub const X_TENANT_ID: &str = "x-tenant-id";
}
pub mod pii {