feat(router): add more payment methods to vault (#333)

This commit is contained in:
ItsMeShashank
2023-01-14 20:48:45 +05:30
committed by GitHub
parent d64e5f2df3
commit 4e00b92dc1
12 changed files with 654 additions and 400 deletions

View File

@ -6,6 +6,7 @@ use utoipa::ToSchema;
pub use self::CreateMerchantAccount as MerchantAccountResponse; pub use self::CreateMerchantAccount as MerchantAccountResponse;
use super::payments::AddressDetails; use super::payments::AddressDetails;
use crate::{enums as api_enums, payment_methods}; use crate::{enums as api_enums, payment_methods};
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct CreateMerchantAccount { pub struct CreateMerchantAccount {

View File

@ -289,3 +289,14 @@ pub struct TokenizedCardValue2 {
pub customer_id: Option<String>, pub customer_id: Option<String>,
pub payment_method_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>,
}

View File

@ -243,7 +243,7 @@ pub enum ConnectorError {
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum CardVaultError { pub enum VaultError {
#[error("Failed to save card in card vault")] #[error("Failed to save card in card vault")]
SaveCardFailed, SaveCardFailed,
#[error("Failed to fetch card details from card vault")] #[error("Failed to fetch card details from card vault")]
@ -254,6 +254,8 @@ pub enum CardVaultError {
ResponseDeserializationFailed, ResponseDeserializationFailed,
#[error("Failed to create payment method")] #[error("Failed to create payment method")]
PaymentMethodCreationFailed, PaymentMethodCreationFailed,
#[error("The given payment method is currently not supported in vault")]
PaymentMethodNotSupported,
#[error("Missing required field: {field_name}")] #[error("Missing required field: {field_name}")]
MissingRequiredField { field_name: String }, MissingRequiredField { field_name: String },
#[error("The card vault returned an unexpected response: {0:?}")] #[error("The card vault returned an unexpected response: {0:?}")]

View File

@ -1,2 +1,3 @@
pub mod cards; pub mod cards;
pub mod transformers; pub mod transformers;
pub mod vault;

View File

@ -7,7 +7,7 @@ use router_env::{instrument, tracing};
use crate::{ use crate::{
core::{ core::{
errors::{self, StorageErrorExt}, errors::{self, StorageErrorExt},
payment_methods::transformers as payment_methods, payment_methods::{transformers as payment_methods, vault},
payments::helpers, payments::helpers,
}, },
db, db,
@ -18,7 +18,7 @@ use crate::{
storage::{self, enums}, storage::{self, enums},
transformers::ForeignInto, transformers::ForeignInto,
}, },
utils::{BytesExt, OptionExt, StringExt}, utils::{BytesExt, OptionExt},
}; };
#[instrument(skip_all)] #[instrument(skip_all)]
@ -133,7 +133,7 @@ pub async fn add_card(
card: api::CardDetail, card: api::CardDetail,
customer_id: String, customer_id: String,
merchant_account: &storage::MerchantAccount, merchant_account: &storage::MerchantAccount,
) -> errors::CustomResult<api::PaymentMethodResponse, errors::CardVaultError> { ) -> errors::CustomResult<api::PaymentMethodResponse, errors::VaultError> {
let locker = &state.conf.locker; let locker = &state.conf.locker;
let db = &*state.store; let db = &*state.store;
let merchant_id = &merchant_account.merchant_id; let merchant_id = &merchant_account.merchant_id;
@ -141,7 +141,7 @@ pub async fn add_card(
.locker_id .locker_id
.to_owned() .to_owned()
.get_required_value("locker_id") .get_required_value("locker_id")
.change_context(errors::CardVaultError::SaveCardFailed)?; .change_context(errors::VaultError::SaveCardFailed)?;
let request = payment_methods::mk_add_card_request( let request = payment_methods::mk_add_card_request(
locker, locker,
@ -154,14 +154,14 @@ pub async fn add_card(
let response = if !locker.mock_locker { let response = if !locker.mock_locker {
let response = services::call_connector_api(state, request) let response = services::call_connector_api(state, request)
.await .await
.change_context(errors::CardVaultError::SaveCardFailed)?; .change_context(errors::VaultError::SaveCardFailed)?;
let response: payment_methods::AddCardResponse = match response { let response: payment_methods::AddCardResponse = match response {
Ok(card) => card Ok(card) => card
.response .response
.parse_struct("AddCardResponse") .parse_struct("AddCardResponse")
.change_context(errors::CardVaultError::ResponseDeserializationFailed), .change_context(errors::VaultError::ResponseDeserializationFailed),
Err(err) => Err(report!(errors::CardVaultError::UnexpectedResponseError( Err(err) => Err(report!(errors::VaultError::UnexpectedResponseError(
err.response err.response
))), ))),
}?; }?;
@ -174,7 +174,7 @@ pub async fn add_card(
if let Some(false) = response.duplicate { if let Some(false) = response.duplicate {
create_payment_method(db, &req, &customer_id, &response.card_id, merchant_id) create_payment_method(db, &req, &customer_id, &response.card_id, merchant_id)
.await .await
.change_context(errors::CardVaultError::PaymentMethodCreationFailed)?; .change_context(errors::VaultError::PaymentMethodCreationFailed)?;
} else { } else {
match db.find_payment_method(&response.card_id).await { match db.find_payment_method(&response.card_id).await {
Ok(_) => (), Ok(_) => (),
@ -182,9 +182,9 @@ pub async fn add_card(
if err.current_context().is_db_not_found() { if err.current_context().is_db_not_found() {
create_payment_method(db, &req, &customer_id, &response.card_id, merchant_id) create_payment_method(db, &req, &customer_id, &response.card_id, merchant_id)
.await .await
.change_context(errors::CardVaultError::PaymentMethodCreationFailed)?; .change_context(errors::VaultError::PaymentMethodCreationFailed)?;
} else { } else {
Err(errors::CardVaultError::PaymentMethodCreationFailed)?; Err(errors::VaultError::PaymentMethodCreationFailed)?;
} }
} }
} }
@ -202,7 +202,7 @@ pub async fn mock_add_card(
card_cvc: Option<String>, card_cvc: Option<String>,
payment_method_id: Option<String>, payment_method_id: Option<String>,
customer_id: Option<&str>, customer_id: Option<&str>,
) -> errors::CustomResult<payment_methods::AddCardResponse, errors::CardVaultError> { ) -> errors::CustomResult<payment_methods::AddCardResponse, errors::VaultError> {
let locker_mock_up = storage::LockerMockUpNew { let locker_mock_up = storage::LockerMockUpNew {
card_id: card_id.to_string(), card_id: card_id.to_string(),
external_id: uuid::Uuid::new_v4().to_string(), external_id: uuid::Uuid::new_v4().to_string(),
@ -220,7 +220,7 @@ pub async fn mock_add_card(
let response = db let response = db
.insert_locker_mock_up(locker_mock_up) .insert_locker_mock_up(locker_mock_up)
.await .await
.change_context(errors::CardVaultError::SaveCardFailed)?; .change_context(errors::VaultError::SaveCardFailed)?;
Ok(payment_methods::AddCardResponse { Ok(payment_methods::AddCardResponse {
card_id: response.card_id, card_id: response.card_id,
external_id: response.external_id, external_id: response.external_id,
@ -241,12 +241,11 @@ pub async fn mock_add_card(
pub async fn mock_get_card<'a>( pub async fn mock_get_card<'a>(
db: &dyn db::StorageInterface, db: &dyn db::StorageInterface,
card_id: &'a str, 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 let locker_mock_up = db
.find_locker_by_card_id(card_id) .find_locker_by_card_id(card_id)
.await .await
.change_context(errors::CardVaultError::FetchCardFailed)?; .change_context(errors::VaultError::FetchCardFailed)?;
let add_card_response = payment_methods::AddCardResponse { let add_card_response = payment_methods::AddCardResponse {
card_id: locker_mock_up card_id: locker_mock_up
.payment_method_id .payment_method_id
@ -275,11 +274,11 @@ pub async fn mock_get_card<'a>(
pub async fn mock_delete_card<'a>( pub async fn mock_delete_card<'a>(
db: &dyn db::StorageInterface, db: &dyn db::StorageInterface,
card_id: &'a str, card_id: &'a str,
) -> errors::CustomResult<payment_methods::DeleteCardResponse, errors::CardVaultError> { ) -> errors::CustomResult<payment_methods::DeleteCardResponse, errors::VaultError> {
let locker_mock_up = db let locker_mock_up = db
.delete_locker_mock_up(card_id) .delete_locker_mock_up(card_id)
.await .await
.change_context(errors::CardVaultError::FetchCardFailed)?; .change_context(errors::VaultError::FetchCardFailed)?;
Ok(payment_methods::DeleteCardResponse { Ok(payment_methods::DeleteCardResponse {
card_id: locker_mock_up.card_id, card_id: locker_mock_up.card_id,
external_id: locker_mock_up.external_id, external_id: locker_mock_up.external_id,
@ -783,7 +782,7 @@ impl BasiliskCardSupport {
) )
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Value2 for locker")?; .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) Ok(card)
} }
} }
@ -840,9 +839,9 @@ pub async fn delete_payment_method(
merchant_account: storage::MerchantAccount, merchant_account: storage::MerchantAccount,
pm: api::PaymentMethodId, pm: api::PaymentMethodId,
) -> errors::RouterResponse<api::DeletePaymentMethodResponse> { ) -> errors::RouterResponse<api::DeletePaymentMethodResponse> {
let (_, value2) = let (_, supplementary_data) =
helpers::Vault::get_payment_method_data_from_locker(state, &pm.payment_method_id).await?; vault::Vault::get_payment_method_data_from_locker(state, &pm.payment_method_id).await?;
let payment_method_id = value2 let payment_method_id = supplementary_data
.payment_method_id .payment_method_id
.map_or(Err(errors::ApiErrorResponse::PaymentMethodNotFound), Ok)?; .map_or(Err(errors::ApiErrorResponse::PaymentMethodNotFound), Ok)?;
let pm = state 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:?}"))),
}
}

View File

@ -70,7 +70,7 @@ pub fn mk_add_card_request(
_req: &api::CreatePaymentMethod, _req: &api::CreatePaymentMethod,
locker_id: &str, locker_id: &str,
_merchant_id: &str, _merchant_id: &str,
) -> CustomResult<services::Request, errors::CardVaultError> { ) -> CustomResult<services::Request, errors::VaultError> {
#[cfg(feature = "sandbox")] #[cfg(feature = "sandbox")]
let customer_id = &format!("{}::{}", customer_id, _merchant_id); let customer_id = &format!("{}::{}", customer_id, _merchant_id);
let add_card_req = AddCardRequest { let add_card_req = AddCardRequest {
@ -84,7 +84,7 @@ pub fn mk_add_card_request(
nickname: Some("router".to_string()), // nickname: Some("router".to_string()), //
}; };
let body = utils::Encode::<AddCardRequest<'_>>::encode(&add_card_req) 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(); let mut url = locker.host.to_owned();
url.push_str("/card/addCard"); url.push_str("/card/addCard");
let mut request = services::Request::new(services::Method::Post, &url); let mut request = services::Request::new(services::Method::Post, &url);
@ -134,14 +134,14 @@ pub fn mk_get_card_request<'a>(
locker: &Locker, locker: &Locker,
locker_id: &'a str, locker_id: &'a str,
card_id: &'a str, card_id: &'a str,
) -> CustomResult<services::Request, errors::CardVaultError> { ) -> CustomResult<services::Request, errors::VaultError> {
let get_card_req = GetCard { let get_card_req = GetCard {
merchant_id: locker_id, merchant_id: locker_id,
card_id, card_id,
}; };
let body = utils::Encode::<GetCard<'_>>::encode(&get_card_req) 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(); let mut url = locker.host.to_owned();
url.push_str("/card/getCard"); url.push_str("/card/getCard");
let mut request = services::Request::new(services::Method::Post, &url); let mut request = services::Request::new(services::Method::Post, &url);
@ -154,13 +154,13 @@ pub fn mk_delete_card_request<'a>(
locker: &Locker, locker: &Locker,
merchant_id: &'a str, merchant_id: &'a str,
card_id: &'a str, card_id: &'a str,
) -> CustomResult<services::Request, errors::CardVaultError> { ) -> CustomResult<services::Request, errors::VaultError> {
let delete_card_req = GetCard { let delete_card_req = GetCard {
merchant_id, merchant_id,
card_id, card_id,
}; };
let body = utils::Encode::<GetCard<'_>>::encode(&delete_card_req) 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(); let mut url = locker.host.to_owned();
url.push_str("/card/deleteCard"); url.push_str("/card/deleteCard");
let mut request = services::Request::new(services::Method::Post, &url); 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( pub fn get_card_detail(
pm: &storage::PaymentMethod, pm: &storage::PaymentMethod,
response: AddCardResponse, response: AddCardResponse,
) -> CustomResult<api::CardDetailFromLocker, errors::CardVaultError> { ) -> CustomResult<api::CardDetailFromLocker, errors::VaultError> {
let card_number = response let card_number = response
.card_number .card_number
.get_required_value("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 mut last4_digits = card_number.peek().to_owned();
let card_detail = api::CardDetailFromLocker { let card_detail = api::CardDetailFromLocker {
scheme: pm.scheme.clone(), scheme: pm.scheme.clone(),
@ -199,9 +199,9 @@ pub fn mk_crud_locker_request(
locker: &Locker, locker: &Locker,
path: &str, path: &str,
req: api::TokenizePayloadEncrypted, req: api::TokenizePayloadEncrypted,
) -> CustomResult<services::Request, errors::CardVaultError> { ) -> CustomResult<services::Request, errors::VaultError> {
let body = utils::Encode::<api::TokenizePayloadEncrypted>::encode_to_value(&req) 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(); let mut url = locker.basilisk_host.to_owned();
url.push_str(path); url.push_str(path);
let mut request = services::Request::new(services::Method::Post, &url); let mut request = services::Request::new(services::Method::Post, &url);
@ -219,7 +219,7 @@ pub fn mk_card_value1(
nickname: Option<String>, nickname: Option<String>,
card_last_four: Option<String>, card_last_four: Option<String>,
card_token: Option<String>, card_token: Option<String>,
) -> CustomResult<String, errors::CardVaultError> { ) -> CustomResult<String, errors::VaultError> {
let value1 = api::TokenizedCardValue1 { let value1 = api::TokenizedCardValue1 {
card_number, card_number,
exp_year, exp_year,
@ -230,7 +230,7 @@ pub fn mk_card_value1(
card_token, card_token,
}; };
let value1_req = utils::Encode::<api::TokenizedCardValue1>::encode_to_string_of_json(&value1) 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) Ok(value1_req)
} }
@ -240,7 +240,7 @@ pub fn mk_card_value2(
external_id: Option<String>, external_id: Option<String>,
customer_id: Option<String>, customer_id: Option<String>,
payment_method_id: Option<String>, payment_method_id: Option<String>,
) -> CustomResult<String, errors::CardVaultError> { ) -> CustomResult<String, errors::VaultError> {
let value2 = api::TokenizedCardValue2 { let value2 = api::TokenizedCardValue2 {
card_security_code, card_security_code,
card_fingerprint, card_fingerprint,
@ -249,6 +249,6 @@ pub fn mk_card_value2(
payment_method_id, payment_method_id,
}; };
let value2_req = utils::Encode::<api::TokenizedCardValue2>::encode_to_string_of_json(&value2) 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) Ok(value2_req)
} }

View 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:?}")),
}
}

View File

@ -20,7 +20,10 @@ use self::{
operations::{BoxedOperation, Operation}, operations::{BoxedOperation, Operation},
}; };
use crate::{ use crate::{
core::errors::{self, RouterResponse, RouterResult}, core::{
errors::{self, RouterResponse, RouterResult},
payment_methods::vault,
},
db::StorageInterface, db::StorageInterface,
logger, pii, logger, pii,
routes::AppState, routes::AppState,
@ -147,7 +150,7 @@ where
.await? .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)) Ok((payment_data, req, customer))
} }

View File

@ -3,7 +3,7 @@ use std::borrow::Cow;
use common_utils::ext_traits::AsyncExt; use common_utils::ext_traits::AsyncExt;
// TODO : Evaluate all the helper functions () // TODO : Evaluate all the helper functions ()
use error_stack::{report, IntoReport, ResultExt}; use error_stack::{report, IntoReport, ResultExt};
use masking::{ExposeOptionInterface, PeekInterface}; use masking::ExposeOptionInterface;
use router_env::{instrument, tracing}; use router_env::{instrument, tracing};
use uuid::Uuid; use uuid::Uuid;
@ -16,10 +16,9 @@ use crate::{
consts, consts,
core::{ core::{
errors::{self, CustomResult, RouterResult, StorageErrorExt}, errors::{self, CustomResult, RouterResult, StorageErrorExt},
payment_methods::cards, payment_methods::{cards, vault},
}, },
db::StorageInterface, db::StorageInterface,
logger,
routes::AppState, routes::AppState,
scheduler::{metrics, workflows::payment_sync}, scheduler::{metrics, workflows::payment_sync},
services, services,
@ -709,7 +708,6 @@ pub async fn make_pm_data<'a, F: Clone, R>(
payment_data: &mut PaymentData<F>, payment_data: &mut PaymentData<F>,
) -> RouterResult<(BoxedOperation<'a, F, R>, Option<api::PaymentMethod>)> { ) -> RouterResult<(BoxedOperation<'a, F, R>, Option<api::PaymentMethod>)> {
let payment_method_type = payment_data.payment_attempt.payment_method; 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 request = &payment_data.payment_method_data;
let token = payment_data.token.clone(); let token = payment_data.token.clone();
let card_cvc = payment_data.card_cvc.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) { let payment_method = match (request, token) {
(_, Some(token)) => Ok::<_, error_stack::Report<errors::ApiErrorResponse>>( (_, Some(token)) => Ok::<_, error_stack::Report<errors::ApiErrorResponse>>(
if payment_method_type == Some(storage_enums::PaymentMethodType::Card) { if payment_method_type == Some(storage_enums::PaymentMethodType::Card) {
// [#196]: Handle token expiry // TODO: Handle token expiry
let (pm, tokenize_value2) = let (pm, supplementary_data) =
Vault::get_payment_method_data_from_locker(state, &token).await?; vault::Vault::get_payment_method_data_from_locker(state, &token).await?;
utils::when( utils::when(
tokenize_value2 supplementary_data
.customer_id .customer_id
.ne(&payment_data.payment_intent.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)) => { (Some(api::PaymentMethod::Card(card)), Some(card_cvc)) => {
let mut updated_card = card; let mut updated_card = card;
updated_card.card_cvc = card_cvc; 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, state,
&token, Some(token),
&updated_card, &updated_pm,
payment_data.payment_intent.customer_id.to_owned(), payment_data.payment_intent.customer_id.to_owned(),
) )
.await?; .await?;
Some(api::PaymentMethod::Card(updated_card)) Some(updated_pm)
} }
(_, _) => pm, (_, _) => pm,
} }
@ -754,194 +753,35 @@ pub async fn make_pm_data<'a, F: Clone, R>(
None None
}, },
), ),
(pm @ Some(api::PaymentMethod::Card(card)), _) => { (pm_opt @ Some(pm @ api::PaymentMethod::Card(_)), _) => {
Vault::store_payment_method_data_in_locker( let token = vault::Vault::store_payment_method_data_in_locker(
state, state,
attempt_id, None,
card, pm,
payment_data.payment_intent.customer_id.to_owned(), payment_data.payment_intent.customer_id.to_owned(),
) )
.await?; .await?;
payment_data.token = Some(attempt_id.to_string()); payment_data.token = Some(token);
Ok(pm.to_owned()) Ok(pm_opt.to_owned())
} }
(pm @ Some(api::PaymentMethod::PayLater(_)), _) => Ok(pm.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(None),
}?; }?;
Ok((operation, payment_method)) 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)] #[instrument(skip_all)]
pub(crate) fn validate_capture_method( pub(crate) fn validate_capture_method(
capture_method: storage_enums::CaptureMethod, capture_method: storage_enums::CaptureMethod,

View File

@ -13,15 +13,19 @@ pub trait ConfigInterface {
&self, &self,
config: storage::ConfigNew, config: storage::ConfigNew,
) -> CustomResult<storage::Config, errors::StorageError>; ) -> CustomResult<storage::Config, errors::StorageError>;
async fn find_config_by_key( async fn find_config_by_key(
&self, &self,
key: &str, key: &str,
) -> CustomResult<storage::Config, errors::StorageError>; ) -> CustomResult<storage::Config, errors::StorageError>;
async fn update_config_by_key( async fn update_config_by_key(
&self, &self,
key: &str, key: &str,
config_update: storage::ConfigUpdate, config_update: storage::ConfigUpdate,
) -> CustomResult<storage::Config, errors::StorageError>; ) -> CustomResult<storage::Config, errors::StorageError>;
async fn delete_config_by_key(&self, key: &str) -> CustomResult<bool, errors::StorageError>;
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -56,6 +60,14 @@ impl ConfigInterface for Store {
.map_err(Into::into) .map_err(Into::into)
.into_report() .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] #[async_trait::async_trait]
@ -84,4 +96,9 @@ impl ConfigInterface for MockDb {
// [#172]: Implement function for `MockDb` // [#172]: Implement function for `MockDb`
Err(errors::StorageError::MockDbError)? 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)?
}
} }

View File

@ -6,7 +6,7 @@ pub use api_models::payment_methods::{
GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCustomerPaymentMethodsResponse, GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCustomerPaymentMethodsResponse,
ListPaymentMethodRequest, ListPaymentMethodResponse, PaymentMethodId, PaymentMethodResponse, ListPaymentMethodRequest, ListPaymentMethodResponse, PaymentMethodId, PaymentMethodResponse,
TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2,
UpdatePaymentMethod, TokenizedWalletValue1, TokenizedWalletValue2, UpdatePaymentMethod,
}; };
use error_stack::report; use error_stack::report;
use literally::hmap; use literally::hmap;

View File

@ -1,10 +1,12 @@
use diesel::associations::HasTable; use diesel::{associations::HasTable, ExpressionMethods};
use router_env::{instrument, tracing}; use router_env::{instrument, tracing};
use super::generics; use super::generics;
use crate::{ use crate::{
configs::{Config, ConfigNew, ConfigUpdate, ConfigUpdateInternal}, configs::{Config, ConfigNew, ConfigUpdate, ConfigUpdateInternal},
errors, PgPooledConn, StorageResult, errors,
schema::configs::dsl,
PgPooledConn, StorageResult,
}; };
impl ConfigNew { impl ConfigNew {
@ -46,4 +48,10 @@ impl Config {
result => result, 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
}
} }