mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
refactor(core): inclusion of locker to store fingerprints (#3630)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Narayan Bhat <narayan.bhat@juspay.in>
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
use common_enums::enums;
|
||||
use common_utils::events::ApiEventMetric;
|
||||
use masking::StrongSecret;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
@ -10,6 +11,15 @@ pub enum BlocklistRequest {
|
||||
ExtendedCardBin(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct GenerateFingerprintRequest {
|
||||
pub card: Card,
|
||||
pub hash_key: StrongSecret<String>,
|
||||
}
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Card {
|
||||
pub card_number: StrongSecret<String>,
|
||||
}
|
||||
pub type AddToBlocklistRequest = BlocklistRequest;
|
||||
pub type DeleteFromBlocklistRequest = BlocklistRequest;
|
||||
|
||||
@ -22,6 +32,11 @@ pub struct BlocklistResponse {
|
||||
pub created_at: time::PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct GenerateFingerprintResponsePayload {
|
||||
pub card_fingerprint: String,
|
||||
}
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct ToggleBlocklistResponse {
|
||||
pub blocklist_guard_status: String,
|
||||
@ -54,4 +69,7 @@ impl ApiEventMetric for BlocklistRequest {}
|
||||
impl ApiEventMetric for BlocklistResponse {}
|
||||
impl ApiEventMetric for ToggleBlocklistResponse {}
|
||||
impl ApiEventMetric for ListBlocklistQuery {}
|
||||
impl ApiEventMetric for GenerateFingerprintRequest {}
|
||||
impl ApiEventMetric for ToggleBlocklistQuery {}
|
||||
impl ApiEventMetric for GenerateFingerprintResponsePayload {}
|
||||
impl ApiEventMetric for Card {}
|
||||
|
||||
@ -160,6 +160,7 @@ pub struct PaymentAttempt {
|
||||
pub unified_code: Option<String>,
|
||||
pub unified_message: Option<String>,
|
||||
pub mandate_data: Option<MandateDetails>,
|
||||
pub fingerprint_id: Option<String>,
|
||||
}
|
||||
|
||||
impl PaymentAttempt {
|
||||
@ -238,6 +239,7 @@ pub struct PaymentAttemptNew {
|
||||
pub unified_code: Option<String>,
|
||||
pub unified_message: Option<String>,
|
||||
pub mandate_data: Option<MandateDetails>,
|
||||
pub fingerprint_id: Option<String>,
|
||||
}
|
||||
|
||||
impl PaymentAttemptNew {
|
||||
@ -270,6 +272,7 @@ pub enum PaymentAttemptUpdate {
|
||||
capture_method: Option<storage_enums::CaptureMethod>,
|
||||
surcharge_amount: Option<i64>,
|
||||
tax_amount: Option<i64>,
|
||||
fingerprint_id: Option<String>,
|
||||
updated_by: String,
|
||||
},
|
||||
UpdateTrackers {
|
||||
@ -307,6 +310,7 @@ pub enum PaymentAttemptUpdate {
|
||||
surcharge_amount: Option<i64>,
|
||||
tax_amount: Option<i64>,
|
||||
merchant_connector_id: Option<String>,
|
||||
fingerprint_id: Option<String>,
|
||||
},
|
||||
RejectUpdate {
|
||||
status: storage_enums::AttemptStatus,
|
||||
|
||||
@ -121,6 +121,7 @@ pub enum PaymentIntentUpdate {
|
||||
amount_captured: Option<i64>,
|
||||
return_url: Option<String>,
|
||||
updated_by: String,
|
||||
fingerprint_id: Option<String>,
|
||||
incremental_authorization_allowed: Option<bool>,
|
||||
},
|
||||
MetadataUpdate {
|
||||
@ -335,6 +336,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
|
||||
// currency,
|
||||
status,
|
||||
amount_captured,
|
||||
fingerprint_id,
|
||||
// customer_id,
|
||||
return_url,
|
||||
updated_by,
|
||||
@ -344,6 +346,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
|
||||
// currency: Some(currency),
|
||||
status: Some(status),
|
||||
amount_captured,
|
||||
fingerprint_id,
|
||||
// customer_id,
|
||||
return_url,
|
||||
modified_at: Some(common_utils::date_time::now()),
|
||||
|
||||
@ -65,6 +65,7 @@ pub struct PaymentAttempt {
|
||||
pub unified_message: Option<String>,
|
||||
pub net_amount: Option<i64>,
|
||||
pub mandate_data: Option<storage_enums::MandateDetails>,
|
||||
pub fingerprint_id: Option<String>,
|
||||
}
|
||||
|
||||
impl PaymentAttempt {
|
||||
@ -140,6 +141,7 @@ pub struct PaymentAttemptNew {
|
||||
pub unified_message: Option<String>,
|
||||
pub net_amount: Option<i64>,
|
||||
pub mandate_data: Option<storage_enums::MandateDetails>,
|
||||
pub fingerprint_id: Option<String>,
|
||||
}
|
||||
|
||||
impl PaymentAttemptNew {
|
||||
@ -177,6 +179,7 @@ pub enum PaymentAttemptUpdate {
|
||||
capture_method: Option<storage_enums::CaptureMethod>,
|
||||
surcharge_amount: Option<i64>,
|
||||
tax_amount: Option<i64>,
|
||||
fingerprint_id: Option<String>,
|
||||
updated_by: String,
|
||||
},
|
||||
UpdateTrackers {
|
||||
@ -212,6 +215,7 @@ pub enum PaymentAttemptUpdate {
|
||||
amount_capturable: Option<i64>,
|
||||
surcharge_amount: Option<i64>,
|
||||
tax_amount: Option<i64>,
|
||||
fingerprint_id: Option<String>,
|
||||
updated_by: String,
|
||||
merchant_connector_id: Option<String>,
|
||||
},
|
||||
@ -351,6 +355,7 @@ pub struct PaymentAttemptUpdateInternal {
|
||||
encoded_data: Option<String>,
|
||||
unified_code: Option<Option<String>>,
|
||||
unified_message: Option<Option<String>>,
|
||||
fingerprint_id: Option<String>,
|
||||
}
|
||||
|
||||
impl PaymentAttemptUpdateInternal {
|
||||
@ -411,6 +416,7 @@ impl PaymentAttemptUpdate {
|
||||
encoded_data,
|
||||
unified_code,
|
||||
unified_message,
|
||||
fingerprint_id,
|
||||
} = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source);
|
||||
PaymentAttempt {
|
||||
amount: amount.unwrap_or(source.amount),
|
||||
@ -452,6 +458,7 @@ impl PaymentAttemptUpdate {
|
||||
encoded_data: encoded_data.or(source.encoded_data),
|
||||
unified_code: unified_code.unwrap_or(source.unified_code),
|
||||
unified_message: unified_message.unwrap_or(source.unified_message),
|
||||
fingerprint_id: fingerprint_id.or(source.fingerprint_id),
|
||||
..source
|
||||
}
|
||||
}
|
||||
@ -476,6 +483,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
||||
capture_method,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
} => Self {
|
||||
amount: Some(amount),
|
||||
@ -494,6 +502,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
||||
capture_method,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
..Default::default()
|
||||
},
|
||||
@ -527,6 +536,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
||||
merchant_connector_id,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
} => Self {
|
||||
amount: Some(amount),
|
||||
currency: Some(currency),
|
||||
@ -549,6 +559,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
||||
merchant_connector_id: merchant_connector_id.map(Some),
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
..Default::default()
|
||||
},
|
||||
PaymentAttemptUpdate::VoidUpdate {
|
||||
|
||||
@ -116,6 +116,7 @@ pub enum PaymentIntentUpdate {
|
||||
ResponseUpdate {
|
||||
status: storage_enums::IntentStatus,
|
||||
amount_captured: Option<i64>,
|
||||
fingerprint_id: Option<String>,
|
||||
return_url: Option<String>,
|
||||
updated_by: String,
|
||||
incremental_authorization_allowed: Option<bool>,
|
||||
@ -405,6 +406,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
|
||||
// currency,
|
||||
status,
|
||||
amount_captured,
|
||||
fingerprint_id,
|
||||
// customer_id,
|
||||
return_url,
|
||||
updated_by,
|
||||
@ -414,6 +416,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
|
||||
// currency: Some(currency),
|
||||
status: Some(status),
|
||||
amount_captured,
|
||||
fingerprint_id,
|
||||
// customer_id,
|
||||
return_url,
|
||||
modified_at: Some(common_utils::date_time::now()),
|
||||
|
||||
@ -689,6 +689,8 @@ diesel::table! {
|
||||
unified_message -> Nullable<Varchar>,
|
||||
net_amount -> Nullable<Int8>,
|
||||
mandate_data -> Nullable<Jsonb>,
|
||||
#[max_length = 64]
|
||||
fingerprint_id -> Nullable<Varchar>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -68,6 +68,7 @@ pub struct PaymentAttemptBatchNew {
|
||||
pub unified_message: Option<String>,
|
||||
pub net_amount: Option<i64>,
|
||||
pub mandate_data: Option<MandateDetails>,
|
||||
pub fingerprint_id: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -122,6 +123,7 @@ impl PaymentAttemptBatchNew {
|
||||
unified_message: self.unified_message,
|
||||
net_amount: self.net_amount,
|
||||
mandate_data: self.mandate_data,
|
||||
fingerprint_id: self.fingerprint_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,28 @@
|
||||
use api_models::blocklist;
|
||||
use api_models::{blocklist, enums as api_enums};
|
||||
use common_utils::{
|
||||
ext_traits::{Encode, StringExt},
|
||||
request::RequestContent,
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use josekit::jwe;
|
||||
#[cfg(feature = "aws_kms")]
|
||||
use masking::PeekInterface;
|
||||
use masking::StrongSecret;
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
use crate::types::{storage, transformers::ForeignFrom};
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::{
|
||||
errors::{self, CustomResult},
|
||||
payment_methods::transformers as payment_methods,
|
||||
},
|
||||
headers, routes,
|
||||
services::{api as services, encryption},
|
||||
types::{storage, transformers::ForeignFrom},
|
||||
utils::ConnectorResponseExt,
|
||||
};
|
||||
|
||||
const LOCKER_FINGERPRINT_PATH: &str = "/cards/fingerprint";
|
||||
|
||||
impl ForeignFrom<storage::Blocklist> for blocklist::AddToBlocklistResponse {
|
||||
fn foreign_from(from: storage::Blocklist) -> Self {
|
||||
@ -11,3 +33,192 @@ impl ForeignFrom<storage::Blocklist> for blocklist::AddToBlocklistResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn generate_fingerprint_request<'a>(
|
||||
#[cfg(not(feature = "aws_kms"))] jwekey: &settings::Jwekey,
|
||||
#[cfg(feature = "aws_kms")] jwekey: &settings::ActiveKmsSecrets,
|
||||
locker: &settings::Locker,
|
||||
payload: &blocklist::GenerateFingerprintRequest,
|
||||
locker_choice: api_enums::LockerChoice,
|
||||
) -> CustomResult<services::Request, errors::VaultError> {
|
||||
let payload = payload
|
||||
.encode_to_vec()
|
||||
.change_context(errors::VaultError::RequestEncodingFailed)?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let private_key = jwekey.jwekey.peek().vault_private_key.as_bytes();
|
||||
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
let private_key = jwekey.vault_private_key.as_bytes();
|
||||
|
||||
let jws = encryption::jws_sign_payload(&payload, &locker.locker_signing_key_id, private_key)
|
||||
.await
|
||||
.change_context(errors::VaultError::RequestEncodingFailed)?;
|
||||
|
||||
let jwe_payload = generate_jwe_payload_for_request(jwekey, &jws, locker_choice).await?;
|
||||
let mut url = match locker_choice {
|
||||
api_enums::LockerChoice::HyperswitchCardVault => locker.host.to_owned(),
|
||||
};
|
||||
url.push_str(LOCKER_FINGERPRINT_PATH);
|
||||
let mut request = services::Request::new(services::Method::Post, &url);
|
||||
request.add_header(headers::CONTENT_TYPE, "application/json".into());
|
||||
request.set_body(RequestContent::Json(Box::new(jwe_payload)));
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
async fn generate_jwe_payload_for_request(
|
||||
#[cfg(feature = "aws_kms")] jwekey: &settings::ActiveKmsSecrets,
|
||||
#[cfg(not(feature = "aws_kms"))] jwekey: &settings::Jwekey,
|
||||
jws: &str,
|
||||
locker_choice: api_enums::LockerChoice,
|
||||
) -> CustomResult<encryption::JweBody, errors::VaultError> {
|
||||
let jws_payload: Vec<&str> = jws.split('.').collect();
|
||||
|
||||
let generate_jws_body = |payload: Vec<&str>| -> Option<encryption::JwsBody> {
|
||||
Some(encryption::JwsBody {
|
||||
header: payload.first()?.to_string(),
|
||||
payload: payload.get(1)?.to_string(),
|
||||
signature: payload.get(2)?.to_string(),
|
||||
})
|
||||
};
|
||||
|
||||
let jws_body =
|
||||
generate_jws_body(jws_payload).ok_or(errors::VaultError::GenerateFingerprintFailed)?;
|
||||
|
||||
let payload = jws_body
|
||||
.encode_to_vec()
|
||||
.change_context(errors::VaultError::GenerateFingerprintFailed)?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let public_key = match locker_choice {
|
||||
api_enums::LockerChoice::HyperswitchCardVault => {
|
||||
jwekey.jwekey.peek().vault_encryption_key.as_bytes()
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
let public_key = match locker_choice {
|
||||
api_enums::LockerChoice::HyperswitchCardVault => jwekey.vault_encryption_key.as_bytes(),
|
||||
};
|
||||
|
||||
let jwe_encrypted = encryption::encrypt_jwe(&payload, public_key)
|
||||
.await
|
||||
.change_context(errors::VaultError::SaveCardFailed)
|
||||
.attach_printable("Error on jwe encrypt")?;
|
||||
let jwe_payload: Vec<&str> = jwe_encrypted.split('.').collect();
|
||||
|
||||
let generate_jwe_body = |payload: Vec<&str>| -> Option<encryption::JweBody> {
|
||||
Some(encryption::JweBody {
|
||||
header: payload.first()?.to_string(),
|
||||
iv: payload.get(2)?.to_string(),
|
||||
encrypted_payload: payload.get(3)?.to_string(),
|
||||
tag: payload.get(4)?.to_string(),
|
||||
encrypted_key: payload.get(1)?.to_string(),
|
||||
})
|
||||
};
|
||||
|
||||
let jwe_body =
|
||||
generate_jwe_body(jwe_payload).ok_or(errors::VaultError::GenerateFingerprintFailed)?;
|
||||
|
||||
Ok(jwe_body)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn generate_fingerprint(
|
||||
state: &routes::AppState,
|
||||
card_number: StrongSecret<String>,
|
||||
hash_key: StrongSecret<String>,
|
||||
locker_choice: api_enums::LockerChoice,
|
||||
) -> CustomResult<blocklist::GenerateFingerprintResponsePayload, errors::VaultError> {
|
||||
let payload = blocklist::GenerateFingerprintRequest {
|
||||
card: blocklist::Card { card_number },
|
||||
hash_key,
|
||||
};
|
||||
|
||||
let generate_fingerprint_resp =
|
||||
call_to_locker_for_fingerprint(state, &payload, locker_choice).await?;
|
||||
|
||||
Ok(generate_fingerprint_resp)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn call_to_locker_for_fingerprint(
|
||||
state: &routes::AppState,
|
||||
payload: &blocklist::GenerateFingerprintRequest,
|
||||
locker_choice: api_enums::LockerChoice,
|
||||
) -> CustomResult<blocklist::GenerateFingerprintResponsePayload, errors::VaultError> {
|
||||
let locker = &state.conf.locker;
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
let jwekey = &state.conf.jwekey;
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let jwekey = &state.kms_secrets;
|
||||
|
||||
let request = generate_fingerprint_request(jwekey, locker, payload, locker_choice).await?;
|
||||
let response = services::call_connector_api(state, request)
|
||||
.await
|
||||
.change_context(errors::VaultError::GenerateFingerprintFailed);
|
||||
let jwe_body: encryption::JweBody = response
|
||||
.get_response_inner("JweBody")
|
||||
.change_context(errors::VaultError::GenerateFingerprintFailed)?;
|
||||
|
||||
let decrypted_payload =
|
||||
decrypt_generate_fingerprint_response_payload(jwekey, jwe_body, Some(locker_choice))
|
||||
.await
|
||||
.change_context(errors::VaultError::GenerateFingerprintFailed)
|
||||
.attach_printable("Error getting decrypted fingerprint response payload")?;
|
||||
let generate_fingerprint_response: blocklist::GenerateFingerprintResponsePayload =
|
||||
decrypted_payload
|
||||
.parse_struct("GenerateFingerprintResponse")
|
||||
.change_context(errors::VaultError::ResponseDeserializationFailed)?;
|
||||
|
||||
Ok(generate_fingerprint_response)
|
||||
}
|
||||
|
||||
async fn decrypt_generate_fingerprint_response_payload(
|
||||
#[cfg(not(feature = "aws_kms"))] jwekey: &settings::Jwekey,
|
||||
#[cfg(feature = "aws_kms")] jwekey: &settings::ActiveKmsSecrets,
|
||||
jwe_body: encryption::JweBody,
|
||||
locker_choice: Option<api_enums::LockerChoice>,
|
||||
) -> CustomResult<String, errors::VaultError> {
|
||||
let target_locker = locker_choice.unwrap_or(api_enums::LockerChoice::HyperswitchCardVault);
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let public_key = match target_locker {
|
||||
api_enums::LockerChoice::HyperswitchCardVault => {
|
||||
jwekey.jwekey.peek().vault_encryption_key.as_bytes()
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let private_key = jwekey.jwekey.peek().vault_private_key.as_bytes();
|
||||
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
let public_key = match target_locker {
|
||||
api_enums::LockerChoice::HyperswitchCardVault => jwekey.vault_encryption_key.as_bytes(),
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
let private_key = jwekey.vault_private_key.as_bytes();
|
||||
|
||||
let jwt = payment_methods::get_dotted_jwe(jwe_body);
|
||||
let alg = jwe::RSA_OAEP;
|
||||
|
||||
let jwe_decrypted = encryption::decrypt_jwe(
|
||||
&jwt,
|
||||
encryption::KeyIdCheck::SkipKeyIdCheck,
|
||||
private_key,
|
||||
alg,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::VaultError::SaveCardFailed)
|
||||
.attach_printable("Jwe Decryption failed for JweBody for vault")?;
|
||||
|
||||
let jws = jwe_decrypted
|
||||
.parse_struct("JwsBody")
|
||||
.change_context(errors::VaultError::ResponseDeserializationFailed)?;
|
||||
let jws_body = payment_methods::get_dotted_jws(jws);
|
||||
|
||||
encryption::verify_sign(jws_body, public_key)
|
||||
.change_context(errors::VaultError::SaveCardFailed)
|
||||
.attach_printable("Jws Decryption failed for JwsBody for vault")
|
||||
}
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
use api_models::blocklist as api_blocklist;
|
||||
use common_enums::MerchantDecision;
|
||||
use common_utils::{
|
||||
crypto::{self, SignMessage},
|
||||
errors::CustomResult,
|
||||
};
|
||||
use common_utils::errors::CustomResult;
|
||||
use diesel_models::configs;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
#[cfg(feature = "aws_kms")]
|
||||
use external_services::aws_kms;
|
||||
use masking::StrongSecret;
|
||||
|
||||
use super::{errors, AppState};
|
||||
use super::{errors, transformers::generate_fingerprint, AppState};
|
||||
use crate::{
|
||||
consts,
|
||||
core::{
|
||||
@ -35,52 +31,13 @@ pub async fn delete_entry_from_blocklist(
|
||||
delete_card_bin_blocklist_entry(state, &xbin, &merchant_id).await?
|
||||
}
|
||||
|
||||
api_blocklist::DeleteFromBlocklistRequest::Fingerprint(fingerprint_id) => {
|
||||
let blocklist_fingerprint = state
|
||||
.store
|
||||
.find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
|
||||
&merchant_id,
|
||||
&fingerprint_id,
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "blocklist record with given fingerprint id not found".to_string(),
|
||||
})?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let decrypted_fingerprint = aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(blocklist_fingerprint.encrypted_fingerprint)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("failed to kms decrypt fingerprint")?;
|
||||
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
let decrypted_fingerprint = blocklist_fingerprint.encrypted_fingerprint;
|
||||
|
||||
let blocklist_entry = state
|
||||
api_blocklist::DeleteFromBlocklistRequest::Fingerprint(fingerprint_id) => state
|
||||
.store
|
||||
.delete_blocklist_entry_by_merchant_id_fingerprint_id(&merchant_id, &fingerprint_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "no blocklist record for the given fingerprint id was found"
|
||||
.to_string(),
|
||||
})?;
|
||||
|
||||
state
|
||||
.store
|
||||
.delete_blocklist_lookup_entry_by_merchant_id_fingerprint(
|
||||
&merchant_id,
|
||||
&decrypted_fingerprint,
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "no blocklist record for the given fingerprint id was found"
|
||||
.to_string(),
|
||||
})?;
|
||||
|
||||
blocklist_entry
|
||||
}
|
||||
message: "no blocklist record for the given fingerprint id was found".to_string(),
|
||||
})?,
|
||||
};
|
||||
|
||||
Ok(blocklist_entry.foreign_into())
|
||||
@ -232,57 +189,20 @@ pub async fn insert_entry_into_blocklist(
|
||||
}
|
||||
}
|
||||
|
||||
let blocklist_fingerprint = state
|
||||
.store
|
||||
.find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
|
||||
&merchant_id,
|
||||
fingerprint_id,
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "fingerprint not found".to_string(),
|
||||
})?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let decrypted_fingerprint = aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(blocklist_fingerprint.encrypted_fingerprint)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("failed to kms decrypt encrypted fingerprint")?;
|
||||
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
let decrypted_fingerprint = blocklist_fingerprint.encrypted_fingerprint;
|
||||
|
||||
state
|
||||
.store
|
||||
.insert_blocklist_lookup_entry(
|
||||
diesel_models::blocklist_lookup::BlocklistLookupNew {
|
||||
merchant_id: merchant_id.clone(),
|
||||
fingerprint: decrypted_fingerprint,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.to_duplicate_response(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "the payment instrument associated with the given fingerprint is already in the blocklist".to_string(),
|
||||
})
|
||||
.attach_printable("failed to add fingerprint to blocklist lookup")?;
|
||||
|
||||
state
|
||||
.store
|
||||
.insert_blocklist_entry(storage::BlocklistNew {
|
||||
merchant_id: merchant_id.clone(),
|
||||
fingerprint_id: fingerprint_id.clone(),
|
||||
data_kind: blocklist_fingerprint.data_kind,
|
||||
data_kind: api_models::enums::enums::BlocklistDataKind::PaymentMethod,
|
||||
metadata: None,
|
||||
created_at: common_utils::date_time::now(),
|
||||
})
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("failed to add fingerprint to pm blocklist")?
|
||||
.attach_printable("failed to add fingerprint to blocklist")?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(blocklist_entry.foreign_into())
|
||||
}
|
||||
|
||||
@ -330,17 +250,6 @@ async fn duplicate_check_insert_bin(
|
||||
merchant_id: &str,
|
||||
data_kind: common_enums::BlocklistDataKind,
|
||||
) -> RouterResult<storage::Blocklist> {
|
||||
let merchant_secret = get_merchant_fingerprint_secret(state, merchant_id).await?;
|
||||
let bin_fingerprint = crypto::HmacSha512::sign_message(
|
||||
&crypto::HmacSha512,
|
||||
merchant_secret.clone().as_bytes(),
|
||||
bin.as_bytes(),
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("error in bin hash creation")?;
|
||||
|
||||
let encoded_fingerprint = hex::encode(bin_fingerprint.clone());
|
||||
|
||||
let blocklist_entry_result = state
|
||||
.store
|
||||
.find_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, bin)
|
||||
@ -363,17 +272,6 @@ async fn duplicate_check_insert_bin(
|
||||
}
|
||||
}
|
||||
|
||||
// Checking for duplicacy
|
||||
state
|
||||
.store
|
||||
.insert_blocklist_lookup_entry(diesel_models::blocklist_lookup::BlocklistLookupNew {
|
||||
merchant_id: merchant_id.to_string(),
|
||||
fingerprint: encoded_fingerprint.clone(),
|
||||
})
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("error inserting blocklist lookup entry")?;
|
||||
|
||||
state
|
||||
.store
|
||||
.insert_blocklist_entry(storage::BlocklistNew {
|
||||
@ -393,21 +291,6 @@ async fn delete_card_bin_blocklist_entry(
|
||||
bin: &str,
|
||||
merchant_id: &str,
|
||||
) -> RouterResult<storage::Blocklist> {
|
||||
let merchant_secret = get_merchant_fingerprint_secret(state, merchant_id).await?;
|
||||
let bin_fingerprint = crypto::HmacSha512
|
||||
.sign_message(merchant_secret.as_bytes(), bin.as_bytes())
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("error when hashing card bin")?;
|
||||
let encoded_fingerprint = hex::encode(bin_fingerprint);
|
||||
|
||||
state
|
||||
.store
|
||||
.delete_blocklist_lookup_entry_by_merchant_id_fingerprint(merchant_id, &encoded_fingerprint)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "could not find a blocklist entry for the given bin".to_string(),
|
||||
})?;
|
||||
|
||||
state
|
||||
.store
|
||||
.delete_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, bin)
|
||||
@ -431,16 +314,16 @@ where
|
||||
get_merchant_fingerprint_secret(state, merchant_id.as_str()).await?;
|
||||
|
||||
// Hashed Fingerprint to check whether or not this payment should be blocked.
|
||||
let card_number_fingerprint = payment_data
|
||||
.payment_method_data
|
||||
.as_ref()
|
||||
.and_then(|pm_data| match pm_data {
|
||||
api_models::payments::PaymentMethodData::Card(card) => {
|
||||
crypto::HmacSha512::sign_message(
|
||||
&crypto::HmacSha512,
|
||||
merchant_fingerprint_secret.as_bytes(),
|
||||
card.card_number.clone().get_card_no().as_bytes(),
|
||||
let card_number_fingerprint = if let Some(api_models::payments::PaymentMethodData::Card(card)) =
|
||||
payment_data.payment_method_data.as_ref()
|
||||
{
|
||||
generate_fingerprint(
|
||||
state,
|
||||
StrongSecret::new(card.card_number.clone().get_card_no()),
|
||||
StrongSecret::new(merchant_fingerprint_secret.clone()),
|
||||
api_models::enums::LockerChoice::HyperswitchCardVault,
|
||||
)
|
||||
.await
|
||||
.attach_printable("error in pm fingerprint creation")
|
||||
.map_or_else(
|
||||
|err| {
|
||||
@ -449,10 +332,10 @@ where
|
||||
},
|
||||
Some,
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.map(hex::encode);
|
||||
.map(|payload| payload.card_fingerprint)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Hashed Cardbin to check whether or not this payment should be blocked.
|
||||
let card_bin_fingerprint = payment_data
|
||||
@ -460,66 +343,43 @@ where
|
||||
.as_ref()
|
||||
.and_then(|pm_data| match pm_data {
|
||||
api_models::payments::PaymentMethodData::Card(card) => {
|
||||
crypto::HmacSha512::sign_message(
|
||||
&crypto::HmacSha512,
|
||||
merchant_fingerprint_secret.as_bytes(),
|
||||
card.card_number.clone().get_card_isin().as_bytes(),
|
||||
)
|
||||
.attach_printable("error in card bin hash creation")
|
||||
.map_or_else(
|
||||
|err| {
|
||||
logger::error!(error=?err);
|
||||
None
|
||||
},
|
||||
Some,
|
||||
)
|
||||
Some(card.card_number.clone().get_card_isin())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.map(hex::encode);
|
||||
});
|
||||
|
||||
// Hashed Extended Cardbin to check whether or not this payment should be blocked.
|
||||
let extended_card_bin_fingerprint = payment_data
|
||||
let extended_card_bin_fingerprint =
|
||||
payment_data
|
||||
.payment_method_data
|
||||
.as_ref()
|
||||
.and_then(|pm_data| match pm_data {
|
||||
api_models::payments::PaymentMethodData::Card(card) => {
|
||||
crypto::HmacSha512::sign_message(
|
||||
&crypto::HmacSha512,
|
||||
merchant_fingerprint_secret.as_bytes(),
|
||||
card.card_number.clone().get_extended_card_bin().as_bytes(),
|
||||
)
|
||||
.attach_printable("error in extended card bin hash creation")
|
||||
.map_or_else(
|
||||
|err| {
|
||||
logger::error!(error=?err);
|
||||
None
|
||||
},
|
||||
Some,
|
||||
)
|
||||
Some(card.card_number.clone().get_extended_card_bin())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.map(hex::encode);
|
||||
});
|
||||
|
||||
//validating the payment method.
|
||||
let mut blocklist_futures = Vec::new();
|
||||
if let Some(card_number_fingerprint) = card_number_fingerprint.as_ref() {
|
||||
blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint(
|
||||
blocklist_futures.push(db.find_blocklist_entry_by_merchant_id_fingerprint_id(
|
||||
merchant_id,
|
||||
card_number_fingerprint,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(card_bin_fingerprint) = card_bin_fingerprint.as_ref() {
|
||||
blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint(
|
||||
blocklist_futures.push(
|
||||
db.find_blocklist_entry_by_merchant_id_fingerprint_id(
|
||||
merchant_id,
|
||||
card_bin_fingerprint,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(extended_card_bin_fingerprint) = extended_card_bin_fingerprint.as_ref() {
|
||||
blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint(
|
||||
blocklist_futures.push(db.find_blocklist_entry_by_merchant_id_fingerprint_id(
|
||||
merchant_id,
|
||||
extended_card_bin_fingerprint,
|
||||
));
|
||||
@ -538,7 +398,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if should_payment_be_blocked {
|
||||
// Update db for attempt and intent status.
|
||||
db.update_payment_intent(
|
||||
@ -582,13 +441,12 @@ where
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
payment_data.payment_intent.fingerprint_id = generate_payment_fingerprint(
|
||||
payment_data.payment_attempt.fingerprint_id = generate_payment_fingerprint(
|
||||
state,
|
||||
payment_data.payment_attempt.merchant_id.clone(),
|
||||
payment_data.payment_method_data.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
@ -598,17 +456,19 @@ pub async fn generate_payment_fingerprint(
|
||||
merchant_id: String,
|
||||
payment_method_data: Option<crate::types::api::PaymentMethodData>,
|
||||
) -> CustomResult<Option<String>, errors::ApiErrorResponse> {
|
||||
let db = &state.store;
|
||||
let merchant_fingerprint_secret = get_merchant_fingerprint_secret(state, &merchant_id).await?;
|
||||
let card_number_fingerprint = payment_method_data
|
||||
.as_ref()
|
||||
.and_then(|pm_data| match pm_data {
|
||||
api_models::payments::PaymentMethodData::Card(card) => {
|
||||
crypto::HmacSha512::sign_message(
|
||||
&crypto::HmacSha512,
|
||||
merchant_fingerprint_secret.as_bytes(),
|
||||
card.card_number.clone().get_card_no().as_bytes(),
|
||||
|
||||
Ok(
|
||||
if let Some(api_models::payments::PaymentMethodData::Card(card)) =
|
||||
payment_method_data.as_ref()
|
||||
{
|
||||
generate_fingerprint(
|
||||
state,
|
||||
StrongSecret::new(card.card_number.clone().get_card_no()),
|
||||
StrongSecret::new(merchant_fingerprint_secret),
|
||||
api_models::enums::LockerChoice::HyperswitchCardVault,
|
||||
)
|
||||
.await
|
||||
.attach_printable("error in pm fingerprint creation")
|
||||
.map_or_else(
|
||||
|err| {
|
||||
@ -617,49 +477,10 @@ pub async fn generate_payment_fingerprint(
|
||||
},
|
||||
Some,
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.map(hex::encode);
|
||||
|
||||
let mut fingerprint_id = None;
|
||||
if let Some(encoded_hash) = card_number_fingerprint {
|
||||
#[cfg(feature = "kms")]
|
||||
let encrypted_fingerprint = kms::get_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.encrypt(encoded_hash)
|
||||
.await
|
||||
.map_or_else(
|
||||
|e| {
|
||||
logger::error!(error=?e, "failed kms encryption of card fingerprint");
|
||||
.map(|payload| payload.card_fingerprint)
|
||||
} else {
|
||||
logger::error!("failed to retrieve card fingerprint");
|
||||
None
|
||||
},
|
||||
Some,
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let encrypted_fingerprint = Some(encoded_hash);
|
||||
|
||||
if let Some(encrypted_fingerprint) = encrypted_fingerprint {
|
||||
fingerprint_id = db
|
||||
.insert_blocklist_fingerprint_entry(
|
||||
diesel_models::blocklist_fingerprint::BlocklistFingerprintNew {
|
||||
merchant_id,
|
||||
fingerprint_id: utils::generate_id(consts::ID_LENGTH, "fingerprint"),
|
||||
encrypted_fingerprint,
|
||||
data_kind: common_enums::BlocklistDataKind::PaymentMethod,
|
||||
created_at: common_utils::date_time::now(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_or_else(
|
||||
|e| {
|
||||
logger::error!(error=?e, "failed storing card fingerprint in db");
|
||||
None
|
||||
},
|
||||
|fp| Some(fp.fingerprint_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(fingerprint_id)
|
||||
}
|
||||
|
||||
@ -236,6 +236,8 @@ pub enum VaultError {
|
||||
FetchPaymentMethodFailed,
|
||||
#[error("Failed to save payment method in vault")]
|
||||
SavePaymentMethodFailed,
|
||||
#[error("Failed to generate fingerprint")]
|
||||
GenerateFingerprintFailed,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@ -3202,6 +3202,7 @@ impl AttemptType {
|
||||
unified_message: None,
|
||||
net_amount: old_payment_attempt.amount,
|
||||
mandate_data: old_payment_attempt.mandate_data,
|
||||
fingerprint_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -739,6 +739,7 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
|
||||
let m_straight_through_algorithm = straight_through_algorithm.clone();
|
||||
let m_error_code = error_code.clone();
|
||||
let m_error_message = error_message.clone();
|
||||
let m_fingerprint_id = payment_data.payment_attempt.fingerprint_id.clone();
|
||||
let m_db = state.clone().store;
|
||||
let surcharge_amount = payment_data
|
||||
.surcharge_details
|
||||
@ -774,6 +775,7 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
|
||||
merchant_connector_id,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id: m_fingerprint_id,
|
||||
},
|
||||
storage_scheme,
|
||||
)
|
||||
@ -784,7 +786,6 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
|
||||
);
|
||||
|
||||
let m_payment_data_payment_intent = payment_data.payment_intent.clone();
|
||||
let m_fingerprint_id = payment_data.payment_intent.fingerprint_id.clone();
|
||||
let m_customer_id = customer_id.clone();
|
||||
let m_shipping_address_id = shipping_address.clone();
|
||||
let m_billing_address_id = billing_address.clone();
|
||||
@ -821,7 +822,7 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
|
||||
metadata: m_metadata,
|
||||
payment_confirm_source: header_payload.payment_confirm_source,
|
||||
updated_by: m_storage_scheme,
|
||||
fingerprint_id: m_fingerprint_id,
|
||||
fingerprint_id: None,
|
||||
session_expiry,
|
||||
},
|
||||
storage_scheme,
|
||||
|
||||
@ -604,6 +604,8 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
};
|
||||
|
||||
if router_data.status == enums::AttemptStatus::Charged {
|
||||
payment_data.payment_intent.fingerprint_id =
|
||||
payment_data.payment_attempt.fingerprint_id.clone();
|
||||
metrics::SUCCESSFUL_PAYMENT.add(&metrics::CONTEXT, 1, &[]);
|
||||
}
|
||||
|
||||
@ -798,6 +800,7 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
return_url: router_data.return_url.clone(),
|
||||
amount_captured,
|
||||
updated_by: storage_scheme.to_string(),
|
||||
fingerprint_id: payment_data.payment_attempt.fingerprint_id.clone(),
|
||||
incremental_authorization_allowed: payment_data
|
||||
.payment_intent
|
||||
.incremental_authorization_allowed,
|
||||
|
||||
@ -559,6 +559,7 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
|
||||
capture_method,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id: None,
|
||||
updated_by: storage_scheme.to_string(),
|
||||
},
|
||||
storage_scheme,
|
||||
|
||||
@ -114,6 +114,7 @@ pub fn proxy_bypass_urls(locker: &Locker) -> Vec<String> {
|
||||
let locker_host_rs = locker.host_rs.to_owned();
|
||||
vec![
|
||||
format!("{locker_host}/cards/add"),
|
||||
format!("{locker_host}/cards/fingerprint"),
|
||||
format!("{locker_host}/cards/retrieve"),
|
||||
format!("{locker_host}/cards/delete"),
|
||||
format!("{locker_host_rs}/cards/add"),
|
||||
|
||||
@ -148,6 +148,7 @@ impl PaymentAttemptInterface for MockDb {
|
||||
unified_code: payment_attempt.unified_code,
|
||||
unified_message: payment_attempt.unified_message,
|
||||
mandate_data: payment_attempt.mandate_data,
|
||||
fingerprint_id: payment_attempt.fingerprint_id,
|
||||
};
|
||||
payment_attempts.push(payment_attempt.clone());
|
||||
Ok(payment_attempt)
|
||||
|
||||
@ -391,6 +391,7 @@ impl<T: DatabaseStore> PaymentAttemptInterface for KVRouterStore<T> {
|
||||
unified_code: payment_attempt.unified_code.clone(),
|
||||
unified_message: payment_attempt.unified_message.clone(),
|
||||
mandate_data: payment_attempt.mandate_data.clone(),
|
||||
fingerprint_id: payment_attempt.fingerprint_id.clone(),
|
||||
};
|
||||
|
||||
let field = format!("pa_{}", created_attempt.attempt_id);
|
||||
@ -1105,6 +1106,7 @@ impl DataModelExt for PaymentAttempt {
|
||||
unified_code: self.unified_code,
|
||||
unified_message: self.unified_message,
|
||||
mandate_data: self.mandate_data.map(|d| d.to_storage_model()),
|
||||
fingerprint_id: self.fingerprint_id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1163,6 +1165,7 @@ impl DataModelExt for PaymentAttempt {
|
||||
mandate_data: storage_model
|
||||
.mandate_data
|
||||
.map(MandateDetails::from_storage_model),
|
||||
fingerprint_id: storage_model.fingerprint_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1219,6 +1222,7 @@ impl DataModelExt for PaymentAttemptNew {
|
||||
unified_code: self.unified_code,
|
||||
unified_message: self.unified_message,
|
||||
mandate_data: self.mandate_data.map(|d| d.to_storage_model()),
|
||||
fingerprint_id: self.fingerprint_id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1275,6 +1279,7 @@ impl DataModelExt for PaymentAttemptNew {
|
||||
mandate_data: storage_model
|
||||
.mandate_data
|
||||
.map(MandateDetails::from_storage_model),
|
||||
fingerprint_id: storage_model.fingerprint_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1299,6 +1304,7 @@ impl DataModelExt for PaymentAttemptUpdate {
|
||||
capture_method,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
} => DieselPaymentAttemptUpdate::Update {
|
||||
amount,
|
||||
@ -1315,6 +1321,7 @@ impl DataModelExt for PaymentAttemptUpdate {
|
||||
capture_method,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
},
|
||||
Self::UpdateTrackers {
|
||||
@ -1373,6 +1380,7 @@ impl DataModelExt for PaymentAttemptUpdate {
|
||||
amount_capturable,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
merchant_connector_id: connector_id,
|
||||
} => DieselPaymentAttemptUpdate::ConfirmUpdate {
|
||||
@ -1394,6 +1402,7 @@ impl DataModelExt for PaymentAttemptUpdate {
|
||||
amount_capturable,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
merchant_connector_id: connector_id,
|
||||
},
|
||||
@ -1578,6 +1587,7 @@ impl DataModelExt for PaymentAttemptUpdate {
|
||||
capture_method,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
} => Self::Update {
|
||||
amount,
|
||||
@ -1594,6 +1604,7 @@ impl DataModelExt for PaymentAttemptUpdate {
|
||||
capture_method,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
},
|
||||
DieselPaymentAttemptUpdate::UpdateTrackers {
|
||||
@ -1641,6 +1652,7 @@ impl DataModelExt for PaymentAttemptUpdate {
|
||||
amount_capturable,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
merchant_connector_id: connector_id,
|
||||
} => Self::ConfirmUpdate {
|
||||
@ -1662,6 +1674,7 @@ impl DataModelExt for PaymentAttemptUpdate {
|
||||
amount_capturable,
|
||||
surcharge_amount,
|
||||
tax_amount,
|
||||
fingerprint_id,
|
||||
updated_by,
|
||||
merchant_connector_id: connector_id,
|
||||
},
|
||||
|
||||
@ -925,12 +925,14 @@ impl DataModelExt for PaymentIntentUpdate {
|
||||
Self::ResponseUpdate {
|
||||
status,
|
||||
amount_captured,
|
||||
fingerprint_id,
|
||||
return_url,
|
||||
updated_by,
|
||||
incremental_authorization_allowed,
|
||||
} => DieselPaymentIntentUpdate::ResponseUpdate {
|
||||
status,
|
||||
amount_captured,
|
||||
fingerprint_id,
|
||||
return_url,
|
||||
updated_by,
|
||||
incremental_authorization_allowed,
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE payment_attempt DROP COLUMN IF EXISTS fingerprint_id;
|
||||
@ -0,0 +1,2 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS fingerprint_id VARCHAR(64);
|
||||
Reference in New Issue
Block a user