diff --git a/Cargo.lock b/Cargo.lock index 6018680728..d0b9894970 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2356,16 +2356,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive", "digest", "fiat-crypto", - "platforms", "rustc_version 0.4.0", "subtle", "zeroize", @@ -5269,12 +5268,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - [[package]] name = "plotters" version = "0.3.5" diff --git a/crates/api_models/src/errors/types.rs b/crates/api_models/src/errors/types.rs index 538a75e1b2..48e9ce5670 100644 --- a/crates/api_models/src/errors/types.rs +++ b/crates/api_models/src/errors/types.rs @@ -77,6 +77,8 @@ pub struct Extra { pub connector: Option, #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub connector_transaction_id: Option, } #[derive(Serialize, Debug, Clone)] diff --git a/crates/common_utils/src/errors.rs b/crates/common_utils/src/errors.rs index e4c59ba2a3..d5e8698504 100644 --- a/crates/common_utils/src/errors.rs +++ b/crates/common_utils/src/errors.rs @@ -69,6 +69,15 @@ pub enum ValidationError { InvalidValue { message: String }, } +/// Integrity check errors. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct IntegrityCheckError { + /// Field names for which integrity check failed! + pub field_names: String, + /// Connector transaction reference id + pub connector_transaction_id: Option, +} + /// Cryptographic algorithm errors #[derive(Debug, thiserror::Error)] pub enum CryptoError { diff --git a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs index faca8cd7bb..782376519c 100644 --- a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs +++ b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs @@ -271,6 +271,12 @@ pub enum ApiErrorResponse { InvalidCookie, #[error(error_type = ErrorType::InvalidRequestError, code = "IR_27", message = "Extended card info does not exist")] ExtendedCardInfoNotFound, + #[error(error_type = ErrorType::ServerNotAvailable, code = "IE", message = "{reason} as data mismatched for {field_names}", ignore = "status_code")] + IntegrityCheckFailed { + reason: String, + field_names: String, + connector_transaction_id: Option, + }, #[error(error_type = ErrorType::ProcessingError, code = "HE_06", message = "Missing tenant id")] MissingTenantId, #[error(error_type = ErrorType::ProcessingError, code = "HE_06", message = "Invalid tenant id: {tenant_id}")] @@ -609,6 +615,19 @@ impl ErrorSwitch for ApiErrorRespon Self::ExtendedCardInfoNotFound => { AER::NotFound(ApiError::new("IR", 27, "Extended card info does not exist", None)) } + Self::IntegrityCheckFailed { + reason, + field_names, + connector_transaction_id + } => AER::InternalServerError(ApiError::new( + "IE", + 0, + format!("{} as data mismatched for {}", reason, field_names), + Some(Extra { + connector_transaction_id: connector_transaction_id.to_owned(), + ..Default::default() + }) + )), Self::MissingTenantId => { AER::InternalServerError(ApiError::new("HE", 6, "Missing Tenant ID in the request".to_string(), None)) } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 79f0c78b7b..edf465e0d2 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, marker::PhantomData}; -use common_utils::{id_type, types::MinorUnit}; +use common_utils::{errors::IntegrityCheckError, id_type, types::MinorUnit}; use masking::Secret; use crate::{payment_address::PaymentAddress, payment_method_data}; @@ -70,6 +70,8 @@ pub struct RouterData { // minor amount for amount framework pub minor_amount_captured: Option, + + pub integrity_check: Result<(), IntegrityCheckError>, } // Different patterns of authentication. diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 8fc32991a3..8496827ff2 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -62,6 +62,23 @@ pub struct PaymentsAuthorizeData { // New amount for amount frame work pub minor_amount: MinorUnit, + pub integrity_object: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct AuthoriseIntegrityObject { + /// Authorise amount + pub amount: MinorUnit, + /// Authorise currency + pub currency: storage_enums::Currency, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyncIntegrityObject { + /// Sync amount + pub amount: Option, + /// Sync currency + pub currency: Option, } #[derive(Debug, serde::Deserialize, Clone)] @@ -348,6 +365,9 @@ pub struct PaymentsSyncData { pub payment_method_type: Option, pub currency: storage_enums::Currency, pub payment_experience: Option, + + pub amount: MinorUnit, + pub integrity_object: Option, } #[derive(Debug, Default, Clone)] diff --git a/crates/hyperswitch_interfaces/src/integrity.rs b/crates/hyperswitch_interfaces/src/integrity.rs new file mode 100644 index 0000000000..e9b9828bb4 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/integrity.rs @@ -0,0 +1,168 @@ +use common_utils::errors::IntegrityCheckError; +use hyperswitch_domain_models::router_request_types::{ + AuthoriseIntegrityObject, PaymentsAuthorizeData, PaymentsSyncData, SyncIntegrityObject, +}; + +/// Connector Integrity trait to check connector data integrity +pub trait FlowIntegrity { + /// Output type for the connector + type IntegrityObject; + /// helps in connector integrity check + fn compare( + req_integrity_object: Self::IntegrityObject, + res_integrity_object: Self::IntegrityObject, + connector_transaction_id: Option, + ) -> Result<(), IntegrityCheckError>; +} + +/// Trait to get connector integrity object based on request and response +pub trait GetIntegrityObject { + /// function to get response integrity object + fn get_response_integrity_object(&self) -> Option; + /// function to get request integrity object + fn get_request_integrity_object(&self) -> T::IntegrityObject; +} + +/// Trait to check flow type, based on which various integrity checks will be performed +pub trait CheckIntegrity { + /// Function to check to initiate integrity check + fn check_integrity( + &self, + request: &Request, + connector_transaction_id: Option, + ) -> Result<(), IntegrityCheckError>; +} + +impl CheckIntegrity for PaymentsAuthorizeData +where + T: FlowIntegrity, + Request: GetIntegrityObject, +{ + fn check_integrity( + &self, + request: &Request, + connector_transaction_id: Option, + ) -> Result<(), IntegrityCheckError> { + match request.get_response_integrity_object() { + Some(res_integrity_object) => { + let req_integrity_object = request.get_request_integrity_object(); + T::compare( + req_integrity_object, + res_integrity_object, + connector_transaction_id, + ) + } + None => Ok(()), + } + } +} + +impl CheckIntegrity for PaymentsSyncData +where + T: FlowIntegrity, + Request: GetIntegrityObject, +{ + fn check_integrity( + &self, + request: &Request, + connector_transaction_id: Option, + ) -> Result<(), IntegrityCheckError> { + match request.get_response_integrity_object() { + Some(res_integrity_object) => { + let req_integrity_object = request.get_request_integrity_object(); + T::compare( + req_integrity_object, + res_integrity_object, + connector_transaction_id, + ) + } + None => Ok(()), + } + } +} + +impl FlowIntegrity for AuthoriseIntegrityObject { + type IntegrityObject = Self; + fn compare( + req_integrity_object: Self, + res_integrity_object: Self, + connector_transaction_id: Option, + ) -> Result<(), IntegrityCheckError> { + let mut mismatched_fields = Vec::new(); + + if req_integrity_object.amount != res_integrity_object.amount { + mismatched_fields.push("amount".to_string()); + } + + if req_integrity_object.currency != res_integrity_object.currency { + mismatched_fields.push("currency".to_string()); + } + + if mismatched_fields.is_empty() { + Ok(()) + } else { + let field_names = mismatched_fields.join(", "); + + Err(IntegrityCheckError { + field_names, + connector_transaction_id, + }) + } + } +} + +impl FlowIntegrity for SyncIntegrityObject { + type IntegrityObject = Self; + fn compare( + req_integrity_object: Self, + res_integrity_object: Self, + connector_transaction_id: Option, + ) -> Result<(), IntegrityCheckError> { + let mut mismatched_fields = Vec::new(); + + if req_integrity_object.amount != res_integrity_object.amount { + mismatched_fields.push("amount".to_string()); + } + + if req_integrity_object.currency != res_integrity_object.currency { + mismatched_fields.push("currency".to_string()); + } + + if mismatched_fields.is_empty() { + Ok(()) + } else { + let field_names = mismatched_fields.join(", "); + + Err(IntegrityCheckError { + field_names, + connector_transaction_id, + }) + } + } +} + +impl GetIntegrityObject for PaymentsAuthorizeData { + fn get_response_integrity_object(&self) -> Option { + self.integrity_object.clone() + } + + fn get_request_integrity_object(&self) -> AuthoriseIntegrityObject { + AuthoriseIntegrityObject { + amount: self.minor_amount, + currency: self.currency, + } + } +} + +impl GetIntegrityObject for PaymentsSyncData { + fn get_response_integrity_object(&self) -> Option { + self.integrity_object.clone() + } + + fn get_request_integrity_object(&self) -> SyncIntegrityObject { + SyncIntegrityObject { + amount: Some(self.amount), + currency: Some(self.currency), + } + } +} diff --git a/crates/hyperswitch_interfaces/src/lib.rs b/crates/hyperswitch_interfaces/src/lib.rs index 66403f641b..7eb35d46a4 100644 --- a/crates/hyperswitch_interfaces/src/lib.rs +++ b/crates/hyperswitch_interfaces/src/lib.rs @@ -9,6 +9,8 @@ pub mod consts; pub mod encryption_interface; pub mod errors; pub mod events; +/// connector integrity check interface +pub mod integrity; pub mod metrics; pub mod secrets_interface; pub mod types; diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index 0e538bb338..f72b71570a 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -264,6 +264,12 @@ pub enum StripeErrorCode { PaymentMethodDeleteFailed, #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Extended card info does not exist")] ExtendedCardInfoNotFound, + #[error(error_type = StripeErrorType::ConnectorError, code = "CE", message = "{reason} as data mismatched for {field_names}")] + IntegrityCheckFailed { + reason: String, + field_names: String, + connector_transaction_id: Option, + }, #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_28", message = "Invalid tenant")] InvalidTenant, #[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert amount to {amount_type} type")] @@ -650,6 +656,15 @@ impl From for StripeErrorCode { Self::InvalidWalletToken { wallet_name } } errors::ApiErrorResponse::ExtendedCardInfoNotFound => Self::ExtendedCardInfoNotFound, + errors::ApiErrorResponse::IntegrityCheckFailed { + reason, + field_names, + connector_transaction_id, + } => Self::IntegrityCheckFailed { + reason, + field_names, + connector_transaction_id, + }, errors::ApiErrorResponse::InvalidTenant { tenant_id: _ } | errors::ApiErrorResponse::MissingTenantId => Self::InvalidTenant, errors::ApiErrorResponse::AmountConversionFailed { amount_type } => { @@ -741,6 +756,7 @@ impl actix_web::ResponseError for StripeErrorCode { Self::ExternalConnectorError { status_code, .. } => { StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) } + Self::IntegrityCheckFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, Self::PaymentBlockedError { code, .. } => { StatusCode::from_u16(*code).unwrap_or(StatusCode::OK) } diff --git a/crates/router/src/connector/cryptopay.rs b/crates/router/src/connector/cryptopay.rs index a3e0844842..40f8584375 100644 --- a/crates/router/src/connector/cryptopay.rs +++ b/crates/router/src/connector/cryptopay.rs @@ -298,7 +298,7 @@ impl ConnectorIntegration Some(utils::convert_back( + Some(ref amount) => Some(utils::convert_back_amount_to_minor_units( self.amount_converter, amount.clone(), data.request.currency, @@ -393,7 +393,7 @@ impl ConnectorIntegration Some(utils::convert_back( + Some(ref amount) => Some(utils::convert_back_amount_to_minor_units( self.amount_converter, amount.clone(), data.request.currency, diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 0874c3b7c5..ba17c44950 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -827,13 +827,23 @@ impl .parse_struct("PaymentIntentSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response_integrity_object = connector_utils::get_sync_integrity_object( + self.amount_converter, + response.amount, + response.currency.clone(), + )?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + let new_router_data = types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, + }); + new_router_data.map(|mut router_data| { + router_data.request.integrity_object = Some(response_integrity_object); + router_data }) } Err(err) => { @@ -1011,13 +1021,25 @@ impl .parse_struct("ChargesResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response_integrity_object = + connector_utils::get_authorise_integrity_object( + self.amount_converter, + response.amount, + response.currency.clone(), + )?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + let new_router_data = types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, + }); + + new_router_data.map(|mut router_data| { + router_data.request.integrity_object = Some(response_integrity_object); + router_data }) } _ => { @@ -1026,13 +1048,25 @@ impl .parse_struct("PaymentIntentResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response_integrity_object = + connector_utils::get_authorise_integrity_object( + self.amount_converter, + response.amount, + response.currency.clone(), + )?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + let new_router_data = types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, + }); + + new_router_data.map(|mut router_data| { + router_data.request.integrity_object = Some(response_integrity_object); + router_data }) } }, @@ -1042,15 +1076,26 @@ impl .parse_struct("PaymentIntentResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response_integrity_object = connector_utils::get_authorise_integrity_object( + self.amount_converter, + response.amount, + response.currency.clone(), + )?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + let new_router_data = types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) - .change_context(errors::ConnectorError::ResponseHandlingFailed) + .change_context(errors::ConnectorError::ResponseHandlingFailed); + + new_router_data.map(|mut router_data| { + router_data.request.integrity_object = Some(response_integrity_object); + router_data + }) } } } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 5c01a483c8..5f2b18f0c0 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; #[cfg(feature = "payouts")] use api_models::payouts::{self, PayoutVendorAccountDetails}; @@ -17,7 +20,11 @@ use common_utils::{ }; use diesel_models::enums; use error_stack::{report, ResultExt}; -use hyperswitch_domain_models::{mandates, payments::payment_attempt::PaymentAttempt}; +use hyperswitch_domain_models::{ + mandates, + payments::payment_attempt::PaymentAttempt, + router_request_types::{AuthoriseIntegrityObject, SyncIntegrityObject}, +}; use masking::{ExposeInterface, Secret}; use once_cell::sync::Lazy; use regex::Regex; @@ -2863,7 +2870,7 @@ pub fn convert_amount( .change_context(errors::ConnectorError::AmountConversionFailed) } -pub fn convert_back( +pub fn convert_back_amount_to_minor_units( amount_convertor: &dyn AmountConvertor, amount: T, currency: enums::Currency, @@ -2872,3 +2879,36 @@ pub fn convert_back( .convert_back(amount, currency) .change_context(errors::ConnectorError::AmountConversionFailed) } + +pub fn get_authorise_integrity_object( + amount_convertor: &dyn AmountConvertor, + amount: T, + currency: String, +) -> Result> { + let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str()) + .change_context(errors::ConnectorError::ParsingFailed)?; + + let amount_in_minor_unit = + convert_back_amount_to_minor_units(amount_convertor, amount, currency_enum)?; + + Ok(AuthoriseIntegrityObject { + amount: amount_in_minor_unit, + currency: currency_enum, + }) +} + +pub fn get_sync_integrity_object( + amount_convertor: &dyn AmountConvertor, + amount: T, + currency: String, +) -> Result> { + let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str()) + .change_context(errors::ConnectorError::ParsingFailed)?; + let amount_in_minor_unit = + convert_back_amount_to_minor_units(amount_convertor, amount, currency_enum)?; + + Ok(SyncIntegrityObject { + amount: Some(amount_in_minor_unit), + currency: Some(currency_enum), + }) +} diff --git a/crates/router/src/core/authentication/transformers.rs b/crates/router/src/core/authentication/transformers.rs index 5111ddcd15..1be40a0ab6 100644 --- a/crates/router/src/core/authentication/transformers.rs +++ b/crates/router/src/core/authentication/transformers.rs @@ -181,6 +181,7 @@ pub fn construct_router_data( refund_id: None, payment_method_status: None, connector_response: None, + integrity_check: Ok(()), }) } diff --git a/crates/router/src/core/fraud_check/flows/checkout_flow.rs b/crates/router/src/core/fraud_check/flows/checkout_flow.rs index 5ba901898f..d897b9e633 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -130,6 +130,7 @@ impl ConstructFlowSpecificData( refund_id: None, dispute_id: None, connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) } diff --git a/crates/router/src/core/fraud_check/flows/record_return.rs b/crates/router/src/core/fraud_check/flows/record_return.rs index 5f2c3f39cc..b44f2d3d4f 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -102,6 +102,7 @@ impl ConstructFlowSpecificData for types::PaymentsAu if self.should_proceed_with_authorize() { self.decide_authentication_type(); logger::debug!(auth_type=?self.auth_type); - let resp = services::execute_connector_processing_step( + let mut new_router_data = services::execute_connector_processing_step( state, connector_integration, &self, @@ -86,8 +86,16 @@ impl Feature for types::PaymentsAu .await .to_payment_failed_response()?; + // Initiating Integrity check + let integrity_result = helpers::check_integrity_based_on_flow( + &new_router_data.request, + &new_router_data.response, + ); + + new_router_data.integrity_check = integrity_result; + metrics::PAYMENT_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics - Ok(resp) + Ok(new_router_data) } else { Ok(self.clone()) } diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index 5b0d1a48e6..336f73e99e 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -85,7 +85,7 @@ impl Feature (types::SyncRequestType::MultipleCaptureSync(_), Err(err)) => Err(err), _ => { // for bulk sync of captures, above logic needs to be handled at connector end - let resp = services::execute_connector_processing_step( + let mut new_router_data = services::execute_connector_processing_step( state, connector_integration, &self, @@ -94,7 +94,16 @@ impl Feature ) .await .to_payment_failed_response()?; - Ok(resp) + + // Initiating Integrity checks + let integrity_result = helpers::check_integrity_based_on_flow( + &new_router_data.request, + &new_router_data.response, + ); + + new_router_data.integrity_check = integrity_result; + + Ok(new_router_data) } } } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 58f061115a..534da72a1d 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -21,6 +21,7 @@ use hyperswitch_domain_models::{ payments::{payment_attempt::PaymentAttempt, payment_intent::CustomerData, PaymentIntent}, router_data::KlarnaSdkResponse, }; +use hyperswitch_interfaces::integrity::{CheckIntegrity, FlowIntegrity, GetIntegrityObject}; use josekit::jwe; use masking::{ExposeInterface, PeekInterface}; use openssl::{ @@ -66,7 +67,7 @@ use crate::{ }, transformers::{ForeignFrom, ForeignTryFrom}, AdditionalPaymentMethodConnectorResponse, ErrorResponse, MandateReference, - RecurringMandatePaymentData, RouterData, + PaymentsResponseData, RecurringMandatePaymentData, RouterData, }, utils::{ self, @@ -3523,6 +3524,7 @@ pub fn router_data_type_conversion( refund_id: router_data.refund_id, dispute_id: router_data.dispute_id, connector_response: router_data.connector_response, + integrity_check: Ok(()), connector_wallets_details: router_data.connector_wallets_details, } } @@ -4946,6 +4948,35 @@ pub fn get_redis_key_for_extended_card_info(merchant_id: &str, payment_id: &str) format!("{merchant_id}_{payment_id}_extended_card_info") } +pub fn check_integrity_based_on_flow( + request: &Request, + payment_response_data: &Result, +) -> Result<(), common_utils::errors::IntegrityCheckError> +where + T: FlowIntegrity, + Request: GetIntegrityObject + CheckIntegrity, +{ + let connector_transaction_id = match payment_response_data { + Ok(resp_data) => match resp_data { + PaymentsResponseData::TransactionResponse { + connector_response_reference_id, + .. + } => connector_response_reference_id, + PaymentsResponseData::TransactionUnresolvedResponse { + connector_response_reference_id, + .. + } => connector_response_reference_id, + PaymentsResponseData::PreProcessingResponse { + connector_response_reference_id, + .. + } => connector_response_reference_id, + _ => &None, + }, + Err(_) => &None, + }; + request.check_integrity(request, connector_transaction_id.to_owned()) +} + pub async fn config_skip_saving_wallet_at_connector( db: &dyn StorageInterface, merchant_id: &String, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 8fb0964da6..a28b9fe3dd 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -7,7 +7,7 @@ use error_stack::{report, ResultExt}; use futures::FutureExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; use router_derive; -use router_env::{instrument, logger, tracing}; +use router_env::{instrument, logger, metrics::add_attributes, tracing}; use storage_impl::DataModelExt; use tracing_futures::Instrument; @@ -868,94 +868,11 @@ async fn payment_response_update_tracker( }; (capture_update, attempt_update) } + Ok(payments_response) => { - let attempt_status = payment_data.payment_attempt.status.to_owned(); - let connector_status = router_data.status.to_owned(); - let updated_attempt_status = match ( - connector_status, - attempt_status, - payment_data.frm_message.to_owned(), - ) { - ( - enums::AttemptStatus::Authorized, - enums::AttemptStatus::Unresolved, - Some(frm_message), - ) => match frm_message.frm_status { - enums::FraudCheckStatus::Fraud | enums::FraudCheckStatus::ManualReview => { - attempt_status - } - _ => router_data.get_attempt_status_for_db_update(&payment_data), - }, - _ => router_data.get_attempt_status_for_db_update(&payment_data), - }; - match payments_response { - types::PaymentsResponseData::PreProcessingResponse { - pre_processing_id, - connector_metadata, - connector_response_reference_id, - .. - } => { - let connector_transaction_id = match pre_processing_id.to_owned() { - types::PreprocessingResponseId::PreProcessingId(_) => None, - types::PreprocessingResponseId::ConnectorTransactionId( - connector_txn_id, - ) => Some(connector_txn_id), - }; - let preprocessing_step_id = match pre_processing_id { - types::PreprocessingResponseId::PreProcessingId(pre_processing_id) => { - Some(pre_processing_id) - } - types::PreprocessingResponseId::ConnectorTransactionId(_) => None, - }; - let payment_attempt_update = - storage::PaymentAttemptUpdate::PreprocessingUpdate { - status: updated_attempt_status, - payment_method_id: payment_data - .payment_attempt - .payment_method_id - .clone(), - connector_metadata, - preprocessing_step_id, - connector_transaction_id, - connector_response_reference_id, - updated_by: storage_scheme.to_string(), - }; - - (None, Some(payment_attempt_update)) - } - types::PaymentsResponseData::TransactionResponse { - resource_id, - redirection_data, - connector_metadata, - connector_response_reference_id, - incremental_authorization_allowed, - charge_id, - .. - } => { - payment_data - .payment_intent - .incremental_authorization_allowed = - core_utils::get_incremental_authorization_allowed_value( - incremental_authorization_allowed, - payment_data - .payment_intent - .request_incremental_authorization, - ); - let connector_transaction_id = match resource_id { - types::ResponseId::NoResponseId => None, - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => Some(id), - }; - - let encoded_data = payment_data.payment_attempt.encoded_data.clone(); - - let authentication_data = redirection_data - .as_ref() - .map(Encode::encode_to_value) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Could not parse the connector response")?; - + // match on connector integrity check + match router_data.integrity_check.clone() { + Err(err) => { let auth_update = if Some(router_data.auth_type) != payment_data.payment_attempt.authentication_type { @@ -963,133 +880,259 @@ async fn payment_response_update_tracker( } else { None }; - - // incase of success, update error code and error message - let error_status = if router_data.status == enums::AttemptStatus::Charged { - Some(None) - } else { - None - }; - - if router_data.status == enums::AttemptStatus::Charged { - payment_data - .payment_intent - .fingerprint_id - .clone_from(&payment_data.payment_attempt.fingerprint_id); - metrics::SUCCESSFUL_PAYMENT.add(&metrics::CONTEXT, 1, &[]); - } - - let payment_method_id = payment_data.payment_attempt.payment_method_id.clone(); - - utils::add_apple_pay_payment_status_metrics( - router_data.status, - router_data.apple_pay_flow.clone(), - payment_data.payment_attempt.connector.clone(), - payment_data.payment_attempt.merchant_id.clone(), - ); - let (capture_updates, payment_attempt_update) = match payment_data - .multiple_capture_data - { - Some(multiple_capture_data) => { - let capture_update = storage::CaptureUpdate::ResponseUpdate { - status: enums::CaptureStatus::foreign_try_from(router_data.status)?, - connector_capture_id: connector_transaction_id.clone(), - connector_response_reference_id, - }; - let capture_update_list = vec![( - multiple_capture_data.get_latest_capture().clone(), - capture_update, - )]; - ( - Some((multiple_capture_data, capture_update_list)), - auth_update.map(|auth_type| { - storage::PaymentAttemptUpdate::AuthenticationTypeUpdate { - authentication_type: auth_type, - updated_by: storage_scheme.to_string(), - } - }), - ) - } - None => ( - None, - Some(storage::PaymentAttemptUpdate::ResponseUpdate { - status: updated_attempt_status, - connector: None, - connector_transaction_id: connector_transaction_id.clone(), - authentication_type: auth_update, - amount_capturable: router_data - .request - .get_amount_capturable(&payment_data, updated_attempt_status) - .map(MinorUnit::new), - payment_method_id, - mandate_id: payment_data.payment_attempt.mandate_id.clone(), - connector_metadata, - payment_token: None, - error_code: error_status.clone(), - error_message: error_status.clone(), - error_reason: error_status.clone(), - unified_code: error_status.clone(), - unified_message: error_status, - connector_response_reference_id, - updated_by: storage_scheme.to_string(), - authentication_data, - encoded_data, - payment_method_data: additional_payment_method_data, - charge_id, - }), - ), - }; - - (capture_updates, payment_attempt_update) - } - types::PaymentsResponseData::TransactionUnresolvedResponse { - resource_id, - reason, - connector_response_reference_id, - } => { - let connector_transaction_id = match resource_id { - types::ResponseId::NoResponseId => None, - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => Some(id), - }; + let field_name = err.field_names; + let connector_transaction_id = err.connector_transaction_id; ( None, - Some(storage::PaymentAttemptUpdate::UnresolvedResponseUpdate { - status: updated_attempt_status, + Some(storage::PaymentAttemptUpdate::ErrorUpdate { connector: None, - connector_transaction_id, - payment_method_id: payment_data - .payment_attempt - .payment_method_id - .clone(), - error_code: Some(reason.clone().map(|cd| cd.code)), - error_message: Some(reason.clone().map(|cd| cd.message)), - error_reason: Some(reason.map(|cd| cd.message)), - connector_response_reference_id, + status: enums::AttemptStatus::Pending, + error_message: Some(Some("Integrity Check Failed!".to_string())), + error_code: Some(Some("IE".to_string())), + error_reason: Some(Some(format!( + "Integrity Check Failed! Value mismatched for fields {field_name}" + ))), + amount_capturable: None, updated_by: storage_scheme.to_string(), + unified_code: None, + unified_message: None, + connector_transaction_id, + payment_method_data: None, + authentication_type: auth_update, }), ) } - types::PaymentsResponseData::SessionResponse { .. } => (None, None), - types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None), - types::PaymentsResponseData::TokenizationResponse { .. } => (None, None), - types::PaymentsResponseData::ConnectorCustomerResponse { .. } => (None, None), - types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => (None, None), - types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => { - (None, None) - } - types::PaymentsResponseData::MultipleCaptureResponse { - capture_sync_response_list, - } => match payment_data.multiple_capture_data { - Some(multiple_capture_data) => { - let capture_update_list = response_to_capture_update( - &multiple_capture_data, + Ok(()) => { + let attempt_status = payment_data.payment_attempt.status.to_owned(); + let connector_status = router_data.status.to_owned(); + let updated_attempt_status = match ( + connector_status, + attempt_status, + payment_data.frm_message.to_owned(), + ) { + ( + enums::AttemptStatus::Authorized, + enums::AttemptStatus::Unresolved, + Some(frm_message), + ) => match frm_message.frm_status { + enums::FraudCheckStatus::Fraud + | enums::FraudCheckStatus::ManualReview => attempt_status, + _ => router_data.get_attempt_status_for_db_update(&payment_data), + }, + _ => router_data.get_attempt_status_for_db_update(&payment_data), + }; + match payments_response { + types::PaymentsResponseData::PreProcessingResponse { + pre_processing_id, + connector_metadata, + connector_response_reference_id, + .. + } => { + let connector_transaction_id = match pre_processing_id.to_owned() { + types::PreprocessingResponseId::PreProcessingId(_) => None, + types::PreprocessingResponseId::ConnectorTransactionId( + connector_txn_id, + ) => Some(connector_txn_id), + }; + let preprocessing_step_id = match pre_processing_id { + types::PreprocessingResponseId::PreProcessingId( + pre_processing_id, + ) => Some(pre_processing_id), + types::PreprocessingResponseId::ConnectorTransactionId(_) => None, + }; + let payment_attempt_update = + storage::PaymentAttemptUpdate::PreprocessingUpdate { + status: updated_attempt_status, + payment_method_id: payment_data + .payment_attempt + .payment_method_id + .clone(), + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + updated_by: storage_scheme.to_string(), + }; + + (None, Some(payment_attempt_update)) + } + types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data, + connector_metadata, + connector_response_reference_id, + incremental_authorization_allowed, + charge_id, + .. + } => { + payment_data + .payment_intent + .incremental_authorization_allowed = + core_utils::get_incremental_authorization_allowed_value( + incremental_authorization_allowed, + payment_data + .payment_intent + .request_incremental_authorization, + ); + let connector_transaction_id = match resource_id { + types::ResponseId::NoResponseId => None, + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => Some(id), + }; + + let encoded_data = payment_data.payment_attempt.encoded_data.clone(); + + let authentication_data = redirection_data + .as_ref() + .map(Encode::encode_to_value) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not parse the connector response")?; + + let auth_update = if Some(router_data.auth_type) + != payment_data.payment_attempt.authentication_type + { + Some(router_data.auth_type) + } else { + None + }; + + // incase of success, update error code and error message + let error_status = + if router_data.status == enums::AttemptStatus::Charged { + Some(None) + } else { + None + }; + + if router_data.status == enums::AttemptStatus::Charged { + payment_data + .payment_intent + .fingerprint_id + .clone_from(&payment_data.payment_attempt.fingerprint_id); + metrics::SUCCESSFUL_PAYMENT.add(&metrics::CONTEXT, 1, &[]); + } + + let payment_method_id = + payment_data.payment_attempt.payment_method_id.clone(); + + utils::add_apple_pay_payment_status_metrics( + router_data.status, + router_data.apple_pay_flow.clone(), + payment_data.payment_attempt.connector.clone(), + payment_data.payment_attempt.merchant_id.clone(), + ); + let (capture_updates, payment_attempt_update) = match payment_data + .multiple_capture_data + { + Some(multiple_capture_data) => { + let capture_update = storage::CaptureUpdate::ResponseUpdate { + status: enums::CaptureStatus::foreign_try_from( + router_data.status, + )?, + connector_capture_id: connector_transaction_id.clone(), + connector_response_reference_id, + }; + let capture_update_list = vec![( + multiple_capture_data.get_latest_capture().clone(), + capture_update, + )]; + (Some((multiple_capture_data, capture_update_list)), auth_update.map(|auth_type| { + storage::PaymentAttemptUpdate::AuthenticationTypeUpdate { + authentication_type: auth_type, + updated_by: storage_scheme.to_string(), + } + })) + } + None => ( + None, + Some(storage::PaymentAttemptUpdate::ResponseUpdate { + status: updated_attempt_status, + connector: None, + connector_transaction_id: connector_transaction_id.clone(), + authentication_type: auth_update, + amount_capturable: router_data + .request + .get_amount_capturable( + &payment_data, + updated_attempt_status, + ) + .map(MinorUnit::new), + payment_method_id, + mandate_id: payment_data.payment_attempt.mandate_id.clone(), + connector_metadata, + payment_token: None, + error_code: error_status.clone(), + error_message: error_status.clone(), + error_reason: error_status.clone(), + unified_code: error_status.clone(), + unified_message: error_status, + connector_response_reference_id, + updated_by: storage_scheme.to_string(), + authentication_data, + encoded_data, + payment_method_data: additional_payment_method_data, + charge_id, + }), + ), + }; + + (capture_updates, payment_attempt_update) + } + types::PaymentsResponseData::TransactionUnresolvedResponse { + resource_id, + reason, + connector_response_reference_id, + } => { + let connector_transaction_id = match resource_id { + types::ResponseId::NoResponseId => None, + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => Some(id), + }; + ( + None, + Some(storage::PaymentAttemptUpdate::UnresolvedResponseUpdate { + status: updated_attempt_status, + connector: None, + connector_transaction_id, + payment_method_id: payment_data + .payment_attempt + .payment_method_id + .clone(), + error_code: Some(reason.clone().map(|cd| cd.code)), + error_message: Some(reason.clone().map(|cd| cd.message)), + error_reason: Some(reason.map(|cd| cd.message)), + connector_response_reference_id, + updated_by: storage_scheme.to_string(), + }), + ) + } + types::PaymentsResponseData::SessionResponse { .. } => (None, None), + types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None), + types::PaymentsResponseData::TokenizationResponse { .. } => (None, None), + types::PaymentsResponseData::ConnectorCustomerResponse { .. } => { + (None, None) + } + types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => { + (None, None) + } + types::PaymentsResponseData::IncrementalAuthorizationResponse { + .. + } => (None, None), + types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, - )?; - (Some((multiple_capture_data, capture_update_list)), None) + } => match payment_data.multiple_capture_data { + Some(multiple_capture_data) => { + let capture_update_list = response_to_capture_update( + &multiple_capture_data, + capture_sync_response_list, + )?; + (Some((multiple_capture_data, capture_update_list)), None) + } + None => (None, None), + }, } - None => (None, None), - }, + } } } }; @@ -1308,7 +1351,33 @@ async fn payment_response_update_tracker( .as_mut() .map(|info| info.status = status) }); - Ok(payment_data) + + match router_data.integrity_check { + Ok(()) => Ok(payment_data), + Err(err) => { + metrics::INTEGRITY_CHECK_FAILED.add( + &metrics::CONTEXT, + 1, + &add_attributes([ + ( + "connector", + payment_data.payment_attempt.connector.unwrap_or_default(), + ), + ("merchant_id", payment_data.payment_attempt.merchant_id), + ]), + ); + Err(error_stack::Report::new( + errors::ApiErrorResponse::IntegrityCheckFailed { + reason: payment_data + .payment_attempt + .error_message + .unwrap_or_default(), + field_names: err.field_names, + connector_transaction_id: payment_data.payment_attempt.connector_transaction_id, + }, + )) + } + } } async fn update_payment_method_status_and_ntid( diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 9fcce65fba..abcb485971 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -195,6 +195,7 @@ where refund_id: None, dispute_id: None, connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) @@ -1349,6 +1350,7 @@ impl TryFrom> for types::PaymentsAuthoriz .transpose()?, customer_acceptance: payment_data.customer_acceptance, charges, + integrity_object: None, }) } } @@ -1358,7 +1360,14 @@ impl TryFrom> for types::PaymentsSyncData fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { let payment_data = additional_data.payment_data; + let amount = payment_data + .surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.final_amount) + .unwrap_or(payment_data.amount.into()); Ok(Self { + amount, + integrity_object: None, mandate_id: payment_data.mandate_id.clone(), connector_transaction_id: match payment_data.payment_attempt.connector_transaction_id { Some(connector_txn_id) => { diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 7c7215b57c..fffe039dab 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -209,6 +209,7 @@ pub async fn construct_payout_router_data<'a, F>( refund_id: None, dispute_id: None, connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) @@ -371,6 +372,7 @@ pub async fn construct_refund_router_data<'a, F>( refund_id: Some(refund.refund_id.clone()), dispute_id: None, connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) @@ -608,6 +610,7 @@ pub async fn construct_accept_dispute_router_data<'a>( dispute_id: Some(dispute.dispute_id.clone()), refund_id: None, connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) } @@ -703,6 +706,7 @@ pub async fn construct_submit_evidence_router_data<'a>( refund_id: None, dispute_id: Some(dispute.dispute_id.clone()), connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) } @@ -804,6 +808,7 @@ pub async fn construct_upload_file_router_data<'a>( refund_id: None, dispute_id: None, connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) } @@ -902,6 +907,7 @@ pub async fn construct_defend_dispute_router_data<'a>( refund_id: None, dispute_id: Some(dispute.dispute_id.clone()), connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) } @@ -989,6 +995,7 @@ pub async fn construct_retrieve_file_router_data<'a>( refund_id: None, dispute_id: None, connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) } diff --git a/crates/router/src/core/webhooks/utils.rs b/crates/router/src/core/webhooks/utils.rs index 3c96dcaf91..e0a0a86c78 100644 --- a/crates/router/src/core/webhooks/utils.rs +++ b/crates/router/src/core/webhooks/utils.rs @@ -119,6 +119,7 @@ pub async fn construct_webhook_router_data<'a>( refund_id: None, dispute_id: None, connector_response: None, + integrity_check: Ok(()), }; Ok(router_data) } diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index 0d55a4b45c..970ba5bd2d 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -131,3 +131,6 @@ counter_metric!(ACCESS_TOKEN_CACHE_HIT, GLOBAL_METER); // A counter to indicate the access token cache miss counter_metric!(ACCESS_TOKEN_CACHE_MISS, GLOBAL_METER); + +// A counter to indicate the integrity check failures +counter_metric!(INTEGRITY_CHECK_FAILED, GLOBAL_METER); diff --git a/crates/router/src/services/conversion_impls.rs b/crates/router/src/services/conversion_impls.rs index d6b8e952b2..741dabe7fa 100644 --- a/crates/router/src/services/conversion_impls.rs +++ b/crates/router/src/services/conversion_impls.rs @@ -71,6 +71,7 @@ fn get_default_router_data( connector_response: None, payment_method_status: None, minor_amount_captured: None, + integrity_check: Ok(()), } } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index f0087ef2f3..e6381e84b1 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -15,7 +15,6 @@ pub mod pm_auth; pub mod storage; pub mod transformers; - use std::marker::PhantomData; pub use api_models::{enums::Connector, mandates}; @@ -790,6 +789,7 @@ impl ForeignFrom<&SetupMandateRouterData> for PaymentsAuthorizeData { authentication_data: None, customer_acceptance: data.request.customer_acceptance.clone(), charges: None, // TODO: allow charges on mandates? + integrity_object: None, } } } @@ -843,6 +843,7 @@ impl ForeignFrom<(&RouterData, T2) dispute_id: data.dispute_id.clone(), refund_id: data.refund_id.clone(), connector_response: data.connector_response.clone(), + integrity_check: Ok(()), } } } @@ -904,6 +905,7 @@ impl refund_id: None, dispute_id: None, connector_response: data.connector_response.clone(), + integrity_check: Ok(()), } } } diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index 61db179277..60b40e7866 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -55,6 +55,7 @@ impl VerifyConnectorData { authentication_data: None, customer_acceptance: None, charges: None, + integrity_object: None, } } @@ -110,6 +111,7 @@ impl VerifyConnectorData { refund_id: None, dispute_id: None, connector_response: None, + integrity_check: Ok(()), } } } diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 51bde094d6..d7eda4db83 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -122,6 +122,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { frm_metadata: None, refund_id: None, dispute_id: None, + integrity_check: Ok(()), } } @@ -186,6 +187,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { frm_metadata: None, refund_id: None, dispute_id: None, + integrity_check: Ok(()), } } diff --git a/crates/router/tests/connectors/bambora.rs b/crates/router/tests/connectors/bambora.rs index 67cd970231..a4cb9b4c5e 100644 --- a/crates/router/tests/connectors/bambora.rs +++ b/crates/router/tests/connectors/bambora.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use common_utils::types::MinorUnit; use masking::Secret; use router::types::{self, domain, storage::enums}; @@ -111,6 +112,8 @@ async fn should_sync_authorized_payment() { payment_method_type: None, currency: enums::Currency::USD, payment_experience: None, + integrity_object: None, + amount: MinorUnit::new(100), }), None, ) @@ -228,6 +231,8 @@ async fn should_sync_auto_captured_payment() { payment_method_type: None, currency: enums::Currency::USD, payment_experience: None, + integrity_object: None, + amount: MinorUnit::new(100), }), None, ) diff --git a/crates/router/tests/connectors/forte.rs b/crates/router/tests/connectors/forte.rs index ef43b3ca8b..45a30614bf 100644 --- a/crates/router/tests/connectors/forte.rs +++ b/crates/router/tests/connectors/forte.rs @@ -1,6 +1,7 @@ use std::{str::FromStr, time::Duration}; use cards::CardNumber; +use common_utils::types::MinorUnit; use masking::Secret; use router::types::{self, api, domain, storage::enums}; @@ -160,6 +161,8 @@ async fn should_sync_authorized_payment() { payment_method_type: None, currency: enums::Currency::USD, payment_experience: None, + integrity_object: None, + amount: MinorUnit::new(100), }), get_default_payment_info(), ) diff --git a/crates/router/tests/connectors/nexinets.rs b/crates/router/tests/connectors/nexinets.rs index 8e98702c79..7909aa9c7f 100644 --- a/crates/router/tests/connectors/nexinets.rs +++ b/crates/router/tests/connectors/nexinets.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use cards::CardNumber; +use common_utils::types::MinorUnit; use masking::Secret; use router::types::{self, domain, storage::enums, PaymentsAuthorizeData}; @@ -127,6 +128,8 @@ async fn should_sync_authorized_payment() { payment_method_type: None, currency: enums::Currency::EUR, payment_experience: None, + integrity_object: None, + amount: MinorUnit::new(100), }), None, ) diff --git a/crates/router/tests/connectors/paypal.rs b/crates/router/tests/connectors/paypal.rs index 0b3d040838..8de891ef09 100644 --- a/crates/router/tests/connectors/paypal.rs +++ b/crates/router/tests/connectors/paypal.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use common_utils::types::MinorUnit; use masking::Secret; use router::types::{self, domain, storage::enums, AccessToken, ConnectorAuthType}; @@ -144,6 +145,8 @@ async fn should_sync_authorized_payment() { payment_method_type: None, currency: enums::Currency::USD, payment_experience: None, + integrity_object: None, + amount: MinorUnit::new(100), }), get_default_payment_info(), ) @@ -343,6 +346,8 @@ async fn should_sync_auto_captured_payment() { payment_method_type: None, currency: enums::Currency::USD, payment_experience: None, + amount: MinorUnit::new(100), + integrity_object: None, }), get_default_payment_info(), ) diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index a9ffdbe697..53bbea865c 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -538,6 +538,7 @@ pub trait ConnectorActions: Connector { refund_id: None, dispute_id: None, connector_response: None, + integrity_check: Ok(()), } } @@ -935,6 +936,7 @@ impl Default for PaymentAuthorizeType { authentication_data: None, customer_acceptance: None, charges: None, + integrity_object: None, }; Self(data) } @@ -994,6 +996,8 @@ impl Default for PaymentSyncType { payment_method_type: None, currency: enums::Currency::USD, payment_experience: None, + amount: MinorUnit::new(100), + integrity_object: None, }; Self(data) } diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index 3dc7b819a9..2b9ea8f821 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use api_models::payments::OrderDetailsWithAmount; use cards::CardNumber; -use common_utils::pii::Email; +use common_utils::{pii::Email, types::MinorUnit}; use masking::Secret; use router::types::{self, domain, storage::enums}; @@ -106,6 +106,8 @@ async fn should_sync_authorized_payment() { payment_method_type: None, currency: enums::Currency::USD, payment_experience: None, + amount: MinorUnit::new(100), + integrity_object: None, }), None, ) @@ -223,6 +225,8 @@ async fn should_sync_auto_captured_payment() { payment_method_type: None, currency: enums::Currency::USD, payment_experience: None, + amount: MinorUnit::new(100), + integrity_object: None, }), None, )