mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 05:17:02 +08:00
feat: integrate Basilisk locker for storing sensitive data (#34)
Co-authored-by: Manoj Ghorela <manoj.ghorela@manoj.ghorela-MacBookPro>
This commit is contained in:
@ -1,7 +1,6 @@
|
||||
use std::{collections::HashSet, hash::Hash};
|
||||
|
||||
use error_stack::{report, ResultExt};
|
||||
use rand::Rng;
|
||||
use router_env::{tracing, tracing::instrument};
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
@ -13,8 +12,7 @@ use crate::{
|
||||
payment_methods::transformers as payment_methods,
|
||||
},
|
||||
db::{
|
||||
merchant_connector_account::IMerchantConnectorAccount, payment_method::IPaymentMethod,
|
||||
temp_card::ITempCard, Db,
|
||||
merchant_connector_account::IMerchantConnectorAccount, payment_method::IPaymentMethod, Db,
|
||||
},
|
||||
pii::prelude::*,
|
||||
routes::AppState,
|
||||
@ -23,7 +21,7 @@ use crate::{
|
||||
api,
|
||||
storage::{self, enums},
|
||||
},
|
||||
utils::{self, BytesExt, OptionExt, ValueExt},
|
||||
utils::{self, BytesExt, OptionExt, StringExt, ValueExt},
|
||||
};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
@ -121,7 +119,8 @@ pub async fn add_card(
|
||||
}?;
|
||||
response
|
||||
} else {
|
||||
mock_add_card(db, &card).await?
|
||||
let card_id = Uuid::new_v4().to_string();
|
||||
mock_add_card(db, &card_id, &card).await?
|
||||
};
|
||||
|
||||
create_payment_method(
|
||||
@ -140,10 +139,11 @@ pub async fn add_card(
|
||||
#[instrument(skip_all)]
|
||||
pub async fn mock_add_card(
|
||||
db: &dyn Db,
|
||||
card_id: &str,
|
||||
card: &api::CardDetail,
|
||||
) -> CustomResult<payment_methods::AddCardResponse, errors::CardVaultError> {
|
||||
let locker_mock_up = storage::LockerMockUpNew {
|
||||
card_id: Uuid::new_v4().to_string(),
|
||||
card_id: card_id.to_string(),
|
||||
external_id: Uuid::new_v4().to_string(),
|
||||
card_fingerprint: Uuid::new_v4().to_string(),
|
||||
card_global_fingerprint: Uuid::new_v4().to_string(),
|
||||
@ -202,7 +202,7 @@ pub async fn mock_get_card<'a>(
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_card<'a>(
|
||||
pub async fn get_card_from_legacy_locker<'a>(
|
||||
state: &'a AppState,
|
||||
merchant_id: &'a str,
|
||||
card_id: &'a str,
|
||||
@ -428,10 +428,9 @@ pub async fn list_customer_payment_method(
|
||||
}
|
||||
let mut vec = Vec::new();
|
||||
for pm in resp.into_iter() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let payment_token = rng.gen::<i32>();
|
||||
let payment_token = Uuid::new_v4().to_string();
|
||||
let card = if pm.payment_method == enums::PaymentMethodType::Card {
|
||||
Some(get_tempcard_from_payment_method(state, payment_token, &pm).await?)
|
||||
Some(get_lookup_key_from_locker(state, &payment_token, &pm).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -461,12 +460,12 @@ pub async fn list_customer_payment_method(
|
||||
Ok(services::BachResponse::Json(response))
|
||||
}
|
||||
|
||||
pub async fn get_tempcard_from_payment_method(
|
||||
pub async fn get_lookup_key_from_locker(
|
||||
state: &AppState,
|
||||
payment_token: i32,
|
||||
payment_token: &str,
|
||||
pm: &storage::PaymentMethod,
|
||||
) -> RouterResult<api::CardDetailFromLocker> {
|
||||
let get_card_resp = get_card(
|
||||
let get_card_resp = get_card_from_legacy_locker(
|
||||
state,
|
||||
pm.merchant_id.as_str(),
|
||||
pm.payment_method_id.as_str(),
|
||||
@ -476,47 +475,91 @@ pub async fn get_tempcard_from_payment_method(
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Get Card Details Failed")?;
|
||||
let card = card_detail.clone();
|
||||
let mut card_info = card_detail
|
||||
.card_number
|
||||
.peek_cloning()
|
||||
.get_required_value("card_number")?;
|
||||
card_info.push_str(":::");
|
||||
card_info.push_str(
|
||||
&card_detail
|
||||
let resp =
|
||||
BasiliskCardSupport::create_payment_method_data_in_locker(state, payment_token, card)
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub struct BasiliskCardSupport;
|
||||
|
||||
#[cfg(not(feature = "basilisk"))]
|
||||
impl BasiliskCardSupport {
|
||||
async fn create_payment_method_data_in_locker(
|
||||
state: &AppState,
|
||||
payment_token: &str,
|
||||
card: api::CardDetailFromLocker,
|
||||
) -> RouterResult<api::CardDetailFromLocker> {
|
||||
let card_number = card
|
||||
.card_number
|
||||
.peek_cloning()
|
||||
.get_required_value("card_number")?;
|
||||
let card_exp_month = card
|
||||
.expiry_month
|
||||
.peek_cloning()
|
||||
.get_required_value("expiry_month")?,
|
||||
);
|
||||
card_info.push_str(":::");
|
||||
card_info.push_str(
|
||||
&card_detail
|
||||
.get_required_value("expiry_month")?;
|
||||
let card_exp_year = card
|
||||
.expiry_year
|
||||
.peek_cloning()
|
||||
.get_required_value("expiry_year")?,
|
||||
);
|
||||
card_info.push_str(":::");
|
||||
card_info.push_str(
|
||||
&card_detail
|
||||
.card_holder_name
|
||||
.peek_cloning()
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
let card_info_val = get_card_info_value(&state.conf.keys, card_info).await?;
|
||||
let temp_card = storage::TempCard {
|
||||
card_info: Some(card_info_val),
|
||||
date_created: common_utils::date_time::now(),
|
||||
txn_id: None,
|
||||
id: payment_token,
|
||||
};
|
||||
.get_required_value("expiry_year")?;
|
||||
let card_holder_name = card.card_holder_name.peek_cloning().unwrap_or_default();
|
||||
let card_detail = api::CardDetail {
|
||||
card_number: card_number.into(),
|
||||
card_exp_month: card_exp_month.into(),
|
||||
card_exp_year: card_exp_year.into(),
|
||||
card_holder_name: Some(card_holder_name.into()),
|
||||
};
|
||||
let db = &state.store;
|
||||
mock_add_card(db, payment_token, &card_detail)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Card Failed")?;
|
||||
Ok(card)
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
.store
|
||||
.insert_tempcard_with_token(temp_card)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
error.to_duplicate_response(errors::ApiErrorResponse::DuplicatePaymentMethod)
|
||||
})?;
|
||||
Ok(card)
|
||||
#[cfg(feature = "basilisk")]
|
||||
impl BasiliskCardSupport {
|
||||
#[instrument(skip_all)]
|
||||
async fn create_payment_method_data_in_locker(
|
||||
state: &AppState,
|
||||
payment_token: &str,
|
||||
card: api::CardDetailFromLocker,
|
||||
) -> RouterResult<api::CardDetailFromLocker> {
|
||||
let card_number = card
|
||||
.card_number
|
||||
.peek_cloning()
|
||||
.get_required_value("card_number")?;
|
||||
let card_exp_month = card
|
||||
.expiry_month
|
||||
.peek_cloning()
|
||||
.get_required_value("expiry_month")?;
|
||||
let card_exp_year = card
|
||||
.expiry_year
|
||||
.peek_cloning()
|
||||
.get_required_value("expiry_year")?;
|
||||
let card_holder_name = card.card_holder_name.peek_cloning().unwrap_or_default();
|
||||
let card_fingerprint = card
|
||||
.card_fingerprint
|
||||
.peek_cloning()
|
||||
.get_required_value("card_fingerprint")?;
|
||||
let value1 = payment_methods::mk_card_value1(
|
||||
card_number,
|
||||
card_exp_year,
|
||||
card_exp_month,
|
||||
Some(card_holder_name),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Error getting Value1 for locker")?;
|
||||
let value2 = payment_methods::mk_card_value2(None, Some(card_fingerprint), None, None)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Error getting Value2 for locker")?;
|
||||
create_tokenize(state, value1, Some(value2), payment_token.to_string()).await?;
|
||||
Ok(card)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_card_info_value(
|
||||
@ -560,7 +603,8 @@ pub async fn retrieve_payment_method(
|
||||
error.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)
|
||||
})?;
|
||||
let card = if pm.payment_method == enums::PaymentMethodType::Card {
|
||||
let get_card_resp = get_card(state, &pm.merchant_id, &pm.payment_method_id).await?;
|
||||
let get_card_resp =
|
||||
get_card_from_legacy_locker(state, &pm.merchant_id, &pm.payment_method_id).await?;
|
||||
let card_detail = payment_methods::get_card_detail(&pm, get_card_resp.card)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
Some(card_detail)
|
||||
@ -615,3 +659,171 @@ pub async fn delete_payment_method(
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
//------------------------------------------------TokenizeService------------------------------------------------
|
||||
pub async fn create_tokenize(
|
||||
state: &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: "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: &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(report!(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable(format!("Got 4xx from the basilisk locker: {err:?}"))),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_tokenized_data(state: &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(report!(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable(format!("Got 4xx from the basilisk locker: {err:?}"))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,3 +182,60 @@ pub fn get_card_detail(
|
||||
};
|
||||
Ok(card_detail)
|
||||
}
|
||||
|
||||
//------------------------------------------------TokenizeService------------------------------------------------
|
||||
pub fn mk_crud_locker_request(
|
||||
locker: &Locker,
|
||||
path: &str,
|
||||
req: api::TokenizePayloadEncrypted,
|
||||
) -> CustomResult<services::Request, errors::CardVaultError> {
|
||||
let body = utils::Encode::<api::TokenizePayloadEncrypted>::encode_to_value(&req)
|
||||
.change_context(errors::CardVaultError::RequestEncodingFailed)?;
|
||||
let mut url = locker.basilisk_host.to_owned();
|
||||
url.push_str(path);
|
||||
let mut request = services::Request::new(services::Method::Post, &url);
|
||||
request.add_header(headers::X_ROUTER, "test");
|
||||
request.add_header(headers::CONTENT_TYPE, "application/json");
|
||||
request.set_body(body.to_string());
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
pub fn mk_card_value1(
|
||||
card_number: String,
|
||||
exp_year: String,
|
||||
exp_month: String,
|
||||
name_on_card: Option<String>,
|
||||
nickname: Option<String>,
|
||||
card_last_four: Option<String>,
|
||||
card_token: Option<String>,
|
||||
) -> CustomResult<String, errors::CardVaultError> {
|
||||
let value1 = api::TokenizedCardValue1 {
|
||||
card_number,
|
||||
exp_year,
|
||||
exp_month,
|
||||
name_on_card,
|
||||
nickname,
|
||||
card_last_four,
|
||||
card_token,
|
||||
};
|
||||
let value1_req = utils::Encode::<api::TokenizedCardValue1>::encode_to_string_of_json(&value1)
|
||||
.change_context(errors::CardVaultError::FetchCardFailed)?;
|
||||
Ok(value1_req)
|
||||
}
|
||||
|
||||
pub fn mk_card_value2(
|
||||
card_security_code: Option<String>,
|
||||
card_fingerprint: Option<String>,
|
||||
external_id: Option<String>,
|
||||
customer_id: Option<String>,
|
||||
) -> CustomResult<String, errors::CardVaultError> {
|
||||
let value2 = api::TokenizedCardValue2 {
|
||||
card_security_code,
|
||||
card_fingerprint,
|
||||
external_id,
|
||||
customer_id,
|
||||
};
|
||||
let value2_req = utils::Encode::<api::TokenizedCardValue2>::encode_to_string_of_json(&value2)
|
||||
.change_context(errors::CardVaultError::FetchCardFailed)?;
|
||||
Ok(value2_req)
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ where
|
||||
&payment_data.payment_attempt.txn_id,
|
||||
&payment_data.payment_attempt,
|
||||
&payment_data.payment_method_data,
|
||||
payment_data.token,
|
||||
&payment_data.token,
|
||||
)
|
||||
.await?;
|
||||
payment_data.payment_method_data = payment_method_data;
|
||||
@ -350,7 +350,7 @@ where
|
||||
pub mandate_id: Option<String>,
|
||||
pub setup_mandate: Option<api::MandateData>,
|
||||
pub address: PaymentAddress,
|
||||
pub token: Option<i32>,
|
||||
pub token: Option<String>,
|
||||
pub confirm: Option<bool>,
|
||||
pub force_sync: Option<bool>,
|
||||
pub payment_method_data: Option<api::PaymentMethod>,
|
||||
|
||||
@ -3,15 +3,15 @@ use std::borrow::Cow;
|
||||
// TODO : Evaluate all the helper functions ()
|
||||
use error_stack::{report, IntoReport, ResultExt};
|
||||
use masking::{PeekInterface, PeekOptionInterface};
|
||||
use rand::Rng;
|
||||
use router_env::{instrument, tracing};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{
|
||||
operations::{BoxedOperation, Operation, PaymentResponse},
|
||||
CustomerDetails, PaymentData,
|
||||
};
|
||||
use crate::{
|
||||
configs::settings::{Keys, Server},
|
||||
configs::settings::Server,
|
||||
core::{
|
||||
errors::{self, CustomResult, RouterResult, StorageErrorExt},
|
||||
payment_methods::cards,
|
||||
@ -97,7 +97,7 @@ pub async fn get_token_pm_type_mandate_details(
|
||||
mandate_type: Option<api::MandateTxnType>,
|
||||
merchant_id: &str,
|
||||
) -> RouterResult<(
|
||||
Option<i32>,
|
||||
Option<String>,
|
||||
Option<enums::PaymentMethodType>,
|
||||
Option<api::MandateData>,
|
||||
)> {
|
||||
@ -108,7 +108,7 @@ pub async fn get_token_pm_type_mandate_details(
|
||||
.clone()
|
||||
.get_required_value("mandate_data")?;
|
||||
Ok((
|
||||
request.payment_token,
|
||||
request.payment_token.to_owned(),
|
||||
request.payment_method,
|
||||
Some(setup_mandate),
|
||||
))
|
||||
@ -119,7 +119,7 @@ pub async fn get_token_pm_type_mandate_details(
|
||||
Ok((token_, payment_method_type_, None))
|
||||
}
|
||||
None => Ok((
|
||||
request.payment_token,
|
||||
request.payment_token.to_owned(),
|
||||
request.payment_method,
|
||||
request.mandate_data.clone(),
|
||||
)),
|
||||
@ -130,7 +130,7 @@ pub async fn get_token_for_recurring_mandate(
|
||||
state: &AppState,
|
||||
req: &api::PaymentsRequest,
|
||||
merchant_id: &str,
|
||||
) -> RouterResult<(Option<i32>, Option<enums::PaymentMethodType>)> {
|
||||
) -> RouterResult<(Option<String>, Option<enums::PaymentMethodType>)> {
|
||||
let db = &state.store;
|
||||
let mandate_id = req.mandate_id.clone().get_required_value("mandate_id")?;
|
||||
|
||||
@ -168,14 +168,9 @@ pub async fn get_token_for_recurring_mandate(
|
||||
error.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)
|
||||
})?;
|
||||
|
||||
let token = rand::thread_rng().gen::<i32>();
|
||||
let token = Uuid::new_v4().to_string();
|
||||
|
||||
let _ = crate::core::payment_methods::cards::get_tempcard_from_payment_method(
|
||||
state,
|
||||
token,
|
||||
&payment_method,
|
||||
)
|
||||
.await?;
|
||||
let _ = cards::get_lookup_key_from_locker(state, &token, &payment_method).await?;
|
||||
|
||||
if let Some(payment_method_from_request) = req.payment_method {
|
||||
if payment_method_from_request != payment_method.payment_method {
|
||||
@ -583,34 +578,20 @@ pub async fn make_pm_data<'a, F: Clone, R>(
|
||||
txn_id: &str,
|
||||
_payment_attempt: &storage::PaymentAttempt,
|
||||
request: &Option<api::PaymentMethod>,
|
||||
token: Option<i32>,
|
||||
token: &Option<String>,
|
||||
) -> RouterResult<(BoxedOperation<'a, F, R>, Option<api::PaymentMethod>)> {
|
||||
let payment_method = match (request, token) {
|
||||
(_, Some(token)) => Ok::<_, error_stack::Report<errors::ApiErrorResponse>>(
|
||||
if payment_method == Some(enums::PaymentMethodType::Card) {
|
||||
// TODO: Handle token expiry
|
||||
payment_method_data_from_temp_card(
|
||||
&state.conf.keys,
|
||||
state
|
||||
.store
|
||||
.find_tempcard_by_token(&token)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
error.to_not_found_response(
|
||||
errors::ApiErrorResponse::PaymentMethodNotFound,
|
||||
)
|
||||
})?,
|
||||
)
|
||||
.await?
|
||||
Vault::get_payment_method_data_from_locker(state, token).await?
|
||||
} else {
|
||||
// TODO: Implement token flow for other payment methods
|
||||
None
|
||||
},
|
||||
),
|
||||
(pm @ Some(api::PaymentMethod::Card(card)), _) => {
|
||||
create_temp_card(state, txn_id, card)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
Vault::store_payment_method_data_in_locker(state, txn_id, card).await?;
|
||||
Ok(pm.to_owned())
|
||||
}
|
||||
(pm @ Some(api::PaymentMethod::PayLater(_)), _) => Ok(pm.to_owned()),
|
||||
@ -620,43 +601,128 @@ pub async fn make_pm_data<'a, F: Clone, R>(
|
||||
|
||||
let payment_method = match payment_method {
|
||||
Some(pm) => Some(pm),
|
||||
None => {
|
||||
let temp_card = state.store.find_tempcard_by_transaction_id(txn_id).await;
|
||||
if let Ok(Some(temp_card)) = temp_card {
|
||||
payment_method_data_from_temp_card(&state.conf.keys, temp_card).await?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => Vault::get_payment_method_data_from_locker(state, txn_id).await?,
|
||||
};
|
||||
|
||||
Ok((operation, payment_method))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn payment_method_data_from_temp_card(
|
||||
keys: &Keys,
|
||||
temp_card: storage::TempCard,
|
||||
) -> RouterResult<Option<api::PaymentMethod>> {
|
||||
match temp_card.card_info {
|
||||
Some(card_info_val) => {
|
||||
let card_info = cards::get_card_info_from_value(keys, card_info_val).await?;
|
||||
let card_info_split: Vec<&str> = card_info.split(":::").collect();
|
||||
pub struct Vault {}
|
||||
|
||||
let card = api::PaymentMethod::Card(api::CCard {
|
||||
card_number: card_info_split[0].to_string().into(),
|
||||
card_exp_month: card_info_split[1].to_string().into(),
|
||||
card_exp_year: card_info_split[2].to_string().into(),
|
||||
card_holder_name: card_info_split[3].to_string().into(),
|
||||
card_cvc: if card_info_split.len() > 4 {
|
||||
card_info_split[4].to_string().into()
|
||||
} else {
|
||||
"".to_string().into()
|
||||
},
|
||||
});
|
||||
Ok(Some(card))
|
||||
}
|
||||
None => Ok(None),
|
||||
#[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>> {
|
||||
let resp = cards::mock_get_card(&state.store, lookup_key)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let card = resp.card;
|
||||
let card_number = card
|
||||
.card_number
|
||||
.peek_cloning()
|
||||
.get_required_value("card_number")?;
|
||||
let card_exp_month = card
|
||||
.card_exp_month
|
||||
.peek_cloning()
|
||||
.get_required_value("expiry_month")?;
|
||||
let card_exp_year = card
|
||||
.card_exp_year
|
||||
.peek_cloning()
|
||||
.get_required_value("expiry_year")?;
|
||||
let card_holder_name = card.name_on_card.peek_cloning().unwrap_or_default();
|
||||
let card = 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".to_string().into(),
|
||||
});
|
||||
Ok(Some(card))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn store_payment_method_data_in_locker(
|
||||
state: &AppState,
|
||||
txn_id: &str,
|
||||
card: &api::CCard,
|
||||
) -> 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)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Card Failed")?;
|
||||
Ok(txn_id.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[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>> {
|
||||
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 = de_tokenize.value2;
|
||||
let card_cvc = if value2.is_empty() {
|
||||
//mandatory field in api contract (when querying from legacy locker we don't get cvv), cvv handling needs to done
|
||||
"".to_string()
|
||||
} else {
|
||||
let tk_value2: api::TokenizedCardValue2 = value2
|
||||
.parse_struct("TokenizedCardValue2")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Error parsing TokenizedCardValue2")?;
|
||||
tk_value2.card_security_code.unwrap_or_default()
|
||||
};
|
||||
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: card_cvc.into(),
|
||||
});
|
||||
Ok(Some(card))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn store_payment_method_data_in_locker(
|
||||
state: &AppState,
|
||||
txn_id: &str,
|
||||
card: &api::CCard,
|
||||
) -> 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, 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -112,7 +112,7 @@ pub trait Domain<F: Clone, R>: Send + Sync {
|
||||
txn_id: &str,
|
||||
payment_attempt: &storage::PaymentAttempt,
|
||||
request: &Option<api::PaymentMethod>,
|
||||
token: Option<i32>,
|
||||
token: &Option<String>,
|
||||
) -> RouterResult<(BoxedOperation<'a, F, R>, Option<api::PaymentMethod>)>;
|
||||
|
||||
async fn add_task_to_process_tracker<'a>(
|
||||
@ -188,7 +188,7 @@ where
|
||||
txn_id: &str,
|
||||
payment_attempt: &storage::PaymentAttempt,
|
||||
request: &Option<api::PaymentMethod>,
|
||||
token: Option<i32>,
|
||||
token: &Option<String>,
|
||||
) -> RouterResult<(
|
||||
BoxedOperation<'a, F, api::PaymentsRequest>,
|
||||
Option<api::PaymentMethod>,
|
||||
@ -276,7 +276,7 @@ where
|
||||
txn_id: &str,
|
||||
payment_attempt: &storage::PaymentAttempt,
|
||||
request: &Option<api::PaymentMethod>,
|
||||
token: Option<i32>,
|
||||
token: &Option<String>,
|
||||
) -> RouterResult<(
|
||||
BoxedOperation<'a, F, api::PaymentsRetrieveRequest>,
|
||||
Option<api::PaymentMethod>,
|
||||
@ -332,7 +332,7 @@ where
|
||||
_txn_id: &str,
|
||||
_payment_attempt: &storage::PaymentAttempt,
|
||||
_request: &Option<api::PaymentMethod>,
|
||||
_token: Option<i32>,
|
||||
_token: &Option<String>,
|
||||
) -> RouterResult<(
|
||||
BoxedOperation<'a, F, api::PaymentsCaptureRequest>,
|
||||
Option<api::PaymentMethod>,
|
||||
@ -380,7 +380,7 @@ where
|
||||
_txn_id: &str,
|
||||
_payment_attempt: &storage::PaymentAttempt,
|
||||
_request: &Option<api::PaymentMethod>,
|
||||
_token: Option<i32>,
|
||||
_token: &Option<String>,
|
||||
) -> RouterResult<(
|
||||
BoxedOperation<'a, F, api::PaymentsCancelRequest>,
|
||||
Option<api::PaymentMethod>,
|
||||
|
||||
@ -224,7 +224,7 @@ where
|
||||
txn_id: &str,
|
||||
payment_attempt: &storage::PaymentAttempt,
|
||||
request: &Option<api::PaymentMethod>,
|
||||
token: Option<i32>,
|
||||
token: &Option<String>,
|
||||
) -> RouterResult<(
|
||||
BoxedOperation<'a, F, api::PaymentsStartRequest>,
|
||||
Option<api::PaymentMethod>,
|
||||
|
||||
@ -7,7 +7,7 @@ use super::payments::{helpers, PaymentAddress};
|
||||
use crate::{
|
||||
consts,
|
||||
core::errors::{self, RouterResult},
|
||||
db::{merchant_connector_account::IMerchantConnectorAccount, temp_card::ITempCard},
|
||||
db::merchant_connector_account::IMerchantConnectorAccount,
|
||||
routes::AppState,
|
||||
types::{
|
||||
self, api,
|
||||
@ -52,16 +52,9 @@ pub async fn construct_refund_router_data<'a, F>(
|
||||
.get_required_value("payment_method_type")?;
|
||||
let payment_method_data = match payment_method_data.cloned() {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
let temp_card = db
|
||||
.find_tempcard_by_transaction_id(&payment_attempt.txn_id)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::PaymentMethodNotFound)?
|
||||
.ok_or(errors::ApiErrorResponse::InvalidCardData { data: None })?;
|
||||
helpers::payment_method_data_from_temp_card(&state.conf.keys, temp_card)
|
||||
.await?
|
||||
.get_required_value("payment_method_data")?
|
||||
}
|
||||
None => helpers::Vault::get_payment_method_data_from_locker(state, &payment_attempt.txn_id)
|
||||
.await?
|
||||
.get_required_value("payment_method_data")?,
|
||||
};
|
||||
|
||||
let router_data = types::RouterData {
|
||||
|
||||
Reference in New Issue
Block a user