Skip to content

Commit

Permalink
feat(router): add integrity check for refund refund sync and capture …
Browse files Browse the repository at this point in the history
…flow with stripe as connector (#5187)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Hrithikesh <[email protected]>
Co-authored-by: Narayan Bhat <[email protected]>
Co-authored-by: Sahkal Poddar <[email protected]>
  • Loading branch information
5 people authored Jul 8, 2024
1 parent 2d31d38 commit adc760f
Show file tree
Hide file tree
Showing 17 changed files with 427 additions and 44 deletions.
3 changes: 3 additions & 0 deletions crates/diesel_models/src/refund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub enum RefundUpdate {
refund_error_message: Option<String>,
refund_error_code: Option<String>,
updated_by: String,
connector_refund_id: Option<String>,
},
ManualUpdate {
refund_status: Option<storage_enums::RefundStatus>,
Expand Down Expand Up @@ -200,11 +201,13 @@ impl From<RefundUpdate> for RefundUpdateInternal {
refund_error_message,
refund_error_code,
updated_by,
connector_refund_id,
} => Self {
refund_status,
refund_error_message,
refund_error_code,
updated_by,
connector_refund_id,
..Default::default()
},
RefundUpdate::ManualUpdate {
Expand Down
21 changes: 21 additions & 0 deletions crates/hyperswitch_domain_models/src/router_request_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub struct SyncIntegrityObject {
pub amount: Option<MinorUnit>,
/// Sync currency
pub currency: Option<storage_enums::Currency>,
/// Sync capture amount in case of automatic capture
pub captured_amount: Option<MinorUnit>,
}

#[derive(Debug, serde::Deserialize, Clone)]
Expand All @@ -108,6 +110,15 @@ pub struct PaymentsCaptureData {
// New amount for amount frame work
pub minor_payment_amount: MinorUnit,
pub minor_amount_to_capture: MinorUnit,
pub integrity_object: Option<CaptureIntegrityObject>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct CaptureIntegrityObject {
/// capture amount
pub capture_amount: Option<MinorUnit>,
/// capture currency
pub currency: storage_enums::Currency,
}

#[derive(Debug, Clone, Default)]
Expand Down Expand Up @@ -373,6 +384,7 @@ pub struct PaymentsSyncData {

pub amount: MinorUnit,
pub integrity_object: Option<SyncIntegrityObject>,
pub captured_amount: Option<MinorUnit>,
}

#[derive(Debug, Default, Clone)]
Expand Down Expand Up @@ -534,6 +546,15 @@ pub struct RefundsData {
// New amount for amount frame work
pub minor_payment_amount: MinorUnit,
pub minor_refund_amount: MinorUnit,
pub integrity_object: Option<RefundIntegrityObject>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct RefundIntegrityObject {
/// refund currency
pub currency: storage_enums::Currency,
/// refund amount
pub refund_amount: MinorUnit,
}

#[derive(Debug, serde::Deserialize, Clone)]
Expand Down
2 changes: 1 addition & 1 deletion crates/hyperswitch_interfaces/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_m
masking = { version = "0.1.0", path = "../masking" }
router_derive = { version = "0.1.0", path = "../router_derive" }
router_env = { version = "0.1.0", path = "../router_env" }
storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false }
storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false }
166 changes: 163 additions & 3 deletions crates/hyperswitch_interfaces/src/integrity.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use common_utils::errors::IntegrityCheckError;
use hyperswitch_domain_models::router_request_types::{
AuthoriseIntegrityObject, PaymentsAuthorizeData, PaymentsSyncData, SyncIntegrityObject,
AuthoriseIntegrityObject, CaptureIntegrityObject, PaymentsAuthorizeData, PaymentsCaptureData,
PaymentsSyncData, RefundIntegrityObject, RefundsData, SyncIntegrityObject,
};

/// Connector Integrity trait to check connector data integrity
Expand Down Expand Up @@ -33,6 +34,30 @@ pub trait CheckIntegrity<Request, T> {
) -> Result<(), IntegrityCheckError>;
}

impl<T, Request> CheckIntegrity<Request, T> for RefundsData
where
T: FlowIntegrity,
Request: GetIntegrityObject<T>,
{
fn check_integrity(
&self,
request: &Request,
connector_refund_id: Option<String>,
) -> 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_refund_id,
)
}
None => Ok(()),
}
}
}

impl<T, Request> CheckIntegrity<Request, T> for PaymentsAuthorizeData
where
T: FlowIntegrity,
Expand All @@ -57,6 +82,30 @@ where
}
}

impl<T, Request> CheckIntegrity<Request, T> for PaymentsCaptureData
where
T: FlowIntegrity,
Request: GetIntegrityObject<T>,
{
fn check_integrity(
&self,
request: &Request,
connector_transaction_id: Option<String>,
) -> 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<T, Request> CheckIntegrity<Request, T> for PaymentsSyncData
where
T: FlowIntegrity,
Expand All @@ -81,6 +130,36 @@ where
}
}

impl FlowIntegrity for RefundIntegrityObject {
type IntegrityObject = Self;
fn compare(
req_integrity_object: Self,
res_integrity_object: Self,
connector_transaction_id: Option<String>,
) -> Result<(), IntegrityCheckError> {
let mut mismatched_fields = Vec::new();

if req_integrity_object.currency != res_integrity_object.currency {
mismatched_fields.push("currency".to_string());
}

if req_integrity_object.refund_amount != res_integrity_object.refund_amount {
mismatched_fields.push("refund_amount".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 AuthoriseIntegrityObject {
type IntegrityObject = Self;
fn compare(
Expand Down Expand Up @@ -120,9 +199,63 @@ impl FlowIntegrity for SyncIntegrityObject {
) -> Result<(), IntegrityCheckError> {
let mut mismatched_fields = Vec::new();

if req_integrity_object.amount != res_integrity_object.amount {
mismatched_fields.push("amount".to_string());
res_integrity_object
.captured_amount
.zip(req_integrity_object.captured_amount)
.map(|tup| {
if tup.0 != tup.1 {
mismatched_fields.push("captured_amount".to_string());
}
});

res_integrity_object
.amount
.zip(req_integrity_object.amount)
.map(|tup| {
if tup.0 != tup.1 {
mismatched_fields.push("amount".to_string());
}
});

res_integrity_object
.currency
.zip(req_integrity_object.currency)
.map(|tup| {
if tup.0 != tup.1 {
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 CaptureIntegrityObject {
type IntegrityObject = Self;
fn compare(
req_integrity_object: Self,
res_integrity_object: Self,
connector_transaction_id: Option<String>,
) -> Result<(), IntegrityCheckError> {
let mut mismatched_fields = Vec::new();

res_integrity_object
.capture_amount
.zip(req_integrity_object.capture_amount)
.map(|tup| {
if tup.0 != tup.1 {
mismatched_fields.push("capture_amount".to_string());
}
});

if req_integrity_object.currency != res_integrity_object.currency {
mismatched_fields.push("currency".to_string());
Expand All @@ -141,6 +274,32 @@ impl FlowIntegrity for SyncIntegrityObject {
}
}

impl GetIntegrityObject<CaptureIntegrityObject> for PaymentsCaptureData {
fn get_response_integrity_object(&self) -> Option<CaptureIntegrityObject> {
self.integrity_object.clone()
}

fn get_request_integrity_object(&self) -> CaptureIntegrityObject {
CaptureIntegrityObject {
capture_amount: Some(self.minor_amount_to_capture),
currency: self.currency,
}
}
}

impl GetIntegrityObject<RefundIntegrityObject> for RefundsData {
fn get_response_integrity_object(&self) -> Option<RefundIntegrityObject> {
self.integrity_object.clone()
}

fn get_request_integrity_object(&self) -> RefundIntegrityObject {
RefundIntegrityObject {
currency: self.currency,
refund_amount: self.minor_refund_amount,
}
}
}

impl GetIntegrityObject<AuthoriseIntegrityObject> for PaymentsAuthorizeData {
fn get_response_integrity_object(&self) -> Option<AuthoriseIntegrityObject> {
self.integrity_object.clone()
Expand All @@ -163,6 +322,7 @@ impl GetIntegrityObject<SyncIntegrityObject> for PaymentsSyncData {
SyncIntegrityObject {
amount: Some(self.amount),
currency: Some(self.currency),
captured_amount: self.captured_amount,
}
}
}
Loading

0 comments on commit adc760f

Please sign in to comment.