mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(router): add more payment methods to vault (#333)
This commit is contained in:
@ -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 {
|
||||
|
||||
@ -289,3 +289,14 @@ pub struct TokenizedCardValue2 {
|
||||
pub customer_id: Option<String>,
|
||||
pub payment_method_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TokenizedWalletValue1 {
|
||||
pub issuer: String,
|
||||
pub token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TokenizedWalletValue2 {
|
||||
pub customer_id: Option<String>,
|
||||
}
|
||||
|
||||
@ -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:?}")]
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
pub mod cards;
|
||||
pub mod transformers;
|
||||
pub mod vault;
|
||||
|
||||
@ -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<api::PaymentMethodResponse, errors::CardVaultError> {
|
||||
) -> errors::CustomResult<api::PaymentMethodResponse, errors::VaultError> {
|
||||
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<String>,
|
||||
payment_method_id: Option<String>,
|
||||
customer_id: Option<&str>,
|
||||
) -> errors::CustomResult<payment_methods::AddCardResponse, errors::CardVaultError> {
|
||||
) -> errors::CustomResult<payment_methods::AddCardResponse, errors::VaultError> {
|
||||
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<String>), errors::CardVaultError>
|
||||
{
|
||||
) -> errors::CustomResult<(payment_methods::GetCardResponse, Option<String>), 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<payment_methods::DeleteCardResponse, errors::CardVaultError> {
|
||||
) -> errors::CustomResult<payment_methods::DeleteCardResponse, errors::VaultError> {
|
||||
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<api::DeletePaymentMethodResponse> {
|
||||
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<String>,
|
||||
lookup_key: String,
|
||||
) -> errors::RouterResult<String> {
|
||||
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<api::TokenizePayloadRequest> {
|
||||
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<String> {
|
||||
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:?}"))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ pub fn mk_add_card_request(
|
||||
_req: &api::CreatePaymentMethod,
|
||||
locker_id: &str,
|
||||
_merchant_id: &str,
|
||||
) -> CustomResult<services::Request, errors::CardVaultError> {
|
||||
) -> CustomResult<services::Request, errors::VaultError> {
|
||||
#[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::<AddCardRequest<'_>>::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<services::Request, errors::CardVaultError> {
|
||||
) -> CustomResult<services::Request, errors::VaultError> {
|
||||
let get_card_req = GetCard {
|
||||
merchant_id: locker_id,
|
||||
card_id,
|
||||
};
|
||||
|
||||
let body = utils::Encode::<GetCard<'_>>::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<services::Request, errors::CardVaultError> {
|
||||
) -> CustomResult<services::Request, errors::VaultError> {
|
||||
let delete_card_req = GetCard {
|
||||
merchant_id,
|
||||
card_id,
|
||||
};
|
||||
let body = utils::Encode::<GetCard<'_>>::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<api::CardDetailFromLocker, errors::CardVaultError> {
|
||||
) -> CustomResult<api::CardDetailFromLocker, errors::VaultError> {
|
||||
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<services::Request, errors::CardVaultError> {
|
||||
) -> CustomResult<services::Request, errors::VaultError> {
|
||||
let body = utils::Encode::<api::TokenizePayloadEncrypted>::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<String>,
|
||||
card_last_four: Option<String>,
|
||||
card_token: Option<String>,
|
||||
) -> CustomResult<String, errors::CardVaultError> {
|
||||
) -> CustomResult<String, errors::VaultError> {
|
||||
let value1 = api::TokenizedCardValue1 {
|
||||
card_number,
|
||||
exp_year,
|
||||
@ -230,7 +230,7 @@ pub fn mk_card_value1(
|
||||
card_token,
|
||||
};
|
||||
let value1_req = utils::Encode::<api::TokenizedCardValue1>::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<String>,
|
||||
customer_id: Option<String>,
|
||||
payment_method_id: Option<String>,
|
||||
) -> CustomResult<String, errors::CardVaultError> {
|
||||
) -> CustomResult<String, errors::VaultError> {
|
||||
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::<api::TokenizedCardValue2>::encode_to_string_of_json(&value2)
|
||||
.change_context(errors::CardVaultError::FetchCardFailed)?;
|
||||
.change_context(errors::VaultError::FetchCardFailed)?;
|
||||
Ok(value2_req)
|
||||
}
|
||||
|
||||
543
crates/router/src/core/payment_methods/vault.rs
Normal file
543
crates/router/src/core/payment_methods/vault.rs
Normal file
@ -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<String>,
|
||||
pub payment_method_id: Option<String>,
|
||||
}
|
||||
|
||||
pub trait Vaultable: Sized {
|
||||
fn get_value1(&self, customer_id: Option<String>) -> CustomResult<String, errors::VaultError>;
|
||||
fn get_value2(&self, _customer_id: Option<String>) -> CustomResult<String, errors::VaultError> {
|
||||
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<String>) -> CustomResult<String, errors::VaultError> {
|
||||
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::<api::TokenizedCardValue1>::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<String>) -> CustomResult<String, errors::VaultError> {
|
||||
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::<api::TokenizedCardValue2>::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<String>) -> CustomResult<String, errors::VaultError> {
|
||||
let value1 = api::TokenizedWalletValue1 {
|
||||
issuer: self.issuer_name.to_string(),
|
||||
token: self.token.clone(),
|
||||
};
|
||||
|
||||
utils::Encode::<api::TokenizedWalletValue1>::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<String>) -> CustomResult<String, errors::VaultError> {
|
||||
let value2 = api::TokenizedWalletValue2 { customer_id };
|
||||
|
||||
utils::Encode::<api::TokenizedWalletValue2>::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<String>) -> CustomResult<String, errors::VaultError> {
|
||||
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::<VaultPaymentMethod>::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<String>) -> CustomResult<String, errors::VaultError> {
|
||||
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::<VaultPaymentMethod>::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<api::PaymentMethod>, 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<String>,
|
||||
payment_method: &api::PaymentMethod,
|
||||
customer_id: Option<String>,
|
||||
) -> RouterResult<String> {
|
||||
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::<MockTokenizeDBValue>::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<String>,
|
||||
) {
|
||||
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<api::PaymentMethod>, 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<String>,
|
||||
payment_method: &api::PaymentMethod,
|
||||
customer_id: Option<String>,
|
||||
) -> RouterResult<String> {
|
||||
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<String>,
|
||||
) {
|
||||
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<String>,
|
||||
lookup_key: String,
|
||||
) -> RouterResult<String> {
|
||||
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::<api::TokenizePayloadRequest>::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<api::TokenizePayloadRequest> {
|
||||
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<String> {
|
||||
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:?}")),
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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<F>,
|
||||
) -> RouterResult<(BoxedOperation<'a, F, R>, Option<api::PaymentMethod>)> {
|
||||
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<errors::ApiErrorResponse>>(
|
||||
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::PaymentMethod>, 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<String>,
|
||||
) -> RouterResult<String> {
|
||||
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<String>,
|
||||
) {
|
||||
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::PaymentMethod>, 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<String>,
|
||||
) -> RouterResult<String> {
|
||||
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<String>,
|
||||
) {
|
||||
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,
|
||||
|
||||
@ -13,15 +13,19 @@ pub trait ConfigInterface {
|
||||
&self,
|
||||
config: storage::ConfigNew,
|
||||
) -> CustomResult<storage::Config, errors::StorageError>;
|
||||
|
||||
async fn find_config_by_key(
|
||||
&self,
|
||||
key: &str,
|
||||
) -> CustomResult<storage::Config, errors::StorageError>;
|
||||
|
||||
async fn update_config_by_key(
|
||||
&self,
|
||||
key: &str,
|
||||
config_update: storage::ConfigUpdate,
|
||||
) -> CustomResult<storage::Config, errors::StorageError>;
|
||||
|
||||
async fn delete_config_by_key(&self, key: &str) -> CustomResult<bool, errors::StorageError>;
|
||||
}
|
||||
|
||||
#[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<bool, errors::StorageError> {
|
||||
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<bool, errors::StorageError> {
|
||||
// [#172]: Implement function for `MockDb`
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<bool> {
|
||||
generics::generic_delete::<<Self as HasTable>::Table, _>(conn, dsl::key.eq(key.to_owned()))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user