diff --git a/crates/api_models/src/disputes.rs b/crates/api_models/src/disputes.rs index f538aa5517..c9e9fd98fe 100644 --- a/crates/api_models/src/disputes.rs +++ b/crates/api_models/src/disputes.rs @@ -4,7 +4,7 @@ use utoipa::ToSchema; use super::enums::{DisputeStage, DisputeStatus}; -#[derive(Default, Clone, Debug, Serialize, ToSchema)] +#[derive(Clone, Debug, Serialize, ToSchema, Eq, PartialEq)] pub struct DisputeResponse { /// The identifier for dispute pub dispute_id: String, @@ -35,12 +35,43 @@ pub struct DisputeResponse { pub challenge_required_by: Option, /// Dispute created time sent by connector #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub created_at: Option, + pub connector_created_at: Option, /// Dispute updated time sent by connector #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub updated_at: Option, + pub connector_updated_at: Option, /// Time at which dispute is received - pub received_at: String, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, +} + +#[derive(Clone, Debug, Serialize, ToSchema, Eq, PartialEq)] +pub struct DisputeResponsePaymentsRetrieve { + /// The identifier for dispute + pub dispute_id: String, + /// Stage of the dispute + pub dispute_stage: DisputeStage, + /// Status of the dispute + pub dispute_status: DisputeStatus, + /// Status of the dispute sent by connector + pub connector_status: String, + /// Dispute id sent by connector + pub connector_dispute_id: String, + /// Reason of dispute sent by connector + pub connector_reason: Option, + /// Reason code of dispute sent by connector + pub connector_reason_code: Option, + /// Evidence deadline of dispute sent by connector + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub challenge_required_by: Option, + /// Dispute created time sent by connector + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub connector_created_at: Option, + /// Dispute updated time sent by connector + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub connector_updated_at: Option, + /// Time at which dispute is received + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, } #[derive(Clone, Debug, Deserialize, ToSchema)] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index bffeb088c7..963f4054fd 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -7,7 +7,7 @@ use router_derive::Setter; use time::PrimitiveDateTime; use utoipa::ToSchema; -use crate::{admin, enums as api_enums, refunds}; +use crate::{admin, disputes, enums as api_enums, refunds}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PaymentOp { @@ -1035,6 +1035,10 @@ pub struct PaymentsResponse { #[schema(value_type = Option>)] pub refunds: Option>, + /// List of dispute that happened on this intent + #[schema(value_type = Option>)] + pub disputes: Option>, + /// A unique identifier to link the payment to a mandate, can be use instead of payment_method_data #[schema(max_length = 255, example = "mandate_iwer89rnjef349dni3")] pub mandate_id: Option, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index f2ab070b62..e9b19be78b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -860,6 +860,7 @@ where pub force_sync: Option, pub payment_method_data: Option, pub refunds: Vec, + pub disputes: Vec, pub sessions_token: Vec, pub card_cvc: Option>, pub email: Option, diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 9c613cf782..1b624eb4ef 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -144,6 +144,7 @@ impl GetTracker, api::PaymentsCancelRequest> payment_method_data: None, force_sync: None, refunds: vec![], + disputes: vec![], connector_response, sessions_token: vec![], card_cvc: None, diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 814839ef2e..8281fc590f 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -150,6 +150,7 @@ impl GetTracker, api::PaymentsCaptu confirm: None, payment_method_data: None, refunds: vec![], + disputes: vec![], connector_response, sessions_token: vec![], card_cvc: None, diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 06cb856fcc..ec6d7f7a38 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -188,6 +188,7 @@ impl GetTracker, api::PaymentsRequest> for Co payment_method_data: request.payment_method_data.clone(), force_sync: None, refunds: vec![], + disputes: vec![], sessions_token: vec![], card_cvc: request.card_cvc.clone(), creds_identifier: None, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index e176805386..4813bbf624 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -207,6 +207,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_method_data: request.payment_method_data.clone(), force_sync: None, refunds: vec![], + disputes: vec![], sessions_token: vec![], card_cvc: request.card_cvc.clone(), creds_identifier, diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index b4bad575c1..0a6b3bd488 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -230,6 +230,7 @@ impl GetTracker, api::PaymentsRequest> for Pa confirm: request.confirm, payment_method_data: request.payment_method_data.clone(), refunds: vec![], + disputes: vec![], force_sync: None, connector_response, sessions_token: vec![], diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs index b4d092db30..d8df71ab8e 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -172,6 +172,7 @@ impl GetTracker, api::VerifyRequest> for Paym address: types::PaymentAddress::default(), force_sync: None, refunds: vec![], + disputes: vec![], sessions_token: vec![], card_cvc: None, creds_identifier, diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 5ad3697aa7..0206ee91ba 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -164,6 +164,7 @@ impl GetTracker, api::PaymentsSessionRequest> payment_method_data: None, force_sync: None, refunds: vec![], + disputes: vec![], sessions_token: vec![], connector_response, card_cvc: None, diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 40c155c147..be798bc600 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -136,6 +136,7 @@ impl GetTracker, api::PaymentsStartRequest> f payment_method_data: None, force_sync: None, refunds: vec![], + disputes: vec![], sessions_token: vec![], card_cvc: None, creds_identifier: None, diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 78bab09e00..22da256adf 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -229,6 +229,14 @@ async fn get_tracker_for_sync< ) })?; + let disputes = db + .find_disputes_by_merchant_id_payment_id(merchant_id, &payment_id_str) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!("Error while retrieving dispute list for, merchant_id: {merchant_id}, payment_id: {payment_id_str}") + })?; + let contains_encoded_data = connector_response.encoded_data.is_some(); let creds_identifier = request @@ -275,6 +283,7 @@ async fn get_tracker_for_sync< ), payment_attempt, refunds, + disputes, sessions_token: vec![], card_cvc: None, creds_identifier, diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 8c691a6194..4f0be30e58 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -287,6 +287,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_method_data: request.payment_method_data.clone(), force_sync: None, refunds: vec![], + disputes: vec![], connector_response, sessions_token: vec![], card_cvc: request.card_cvc.clone(), diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 7945ef1d53..d1a13816f0 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -151,6 +151,7 @@ where payment_data.payment_attempt, payment_data.payment_intent, payment_data.refunds, + payment_data.disputes, payment_data.payment_method_data, customer, auth_flow, @@ -240,6 +241,7 @@ pub fn payments_to_payments_response( payment_attempt: storage::PaymentAttempt, payment_intent: storage::PaymentIntent, refunds: Vec, + disputes: Vec, payment_method_data: Option, customer: Option, auth_flow: services::AuthFlow, @@ -266,6 +268,16 @@ where } else { Some(refunds.into_iter().map(ForeignInto::foreign_into).collect()) }; + let disputes_response = if disputes.is_empty() { + None + } else { + Some( + disputes + .into_iter() + .map(ForeignInto::foreign_into) + .collect(), + ) + }; Ok(match payment_request { Some(_request) => { @@ -346,6 +358,7 @@ where .set_mandate_id(mandate_id) .set_description(payment_intent.description) .set_refunds(refunds_response) // refunds.iter().map(refund_to_refund_response), + .set_disputes(disputes_response) .set_payment_method( payment_attempt .payment_method @@ -417,6 +430,7 @@ where customer_id: payment_intent.customer_id, description: payment_intent.description, refunds: refunds_response, + disputes: disputes_response, payment_method: payment_attempt .payment_method .map(ForeignInto::foreign_into), diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index a2090e3419..ce0e854f8c 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -256,8 +256,8 @@ async fn get_or_update_dispute_object( connector_reason: dispute_details.connector_reason, connector_reason_code: dispute_details.connector_reason_code, challenge_required_by: dispute_details.challenge_required_by, - dispute_created_at: dispute_details.created_at, - updated_at: dispute_details.updated_at, + connector_created_at: dispute_details.created_at, + connector_updated_at: dispute_details.updated_at, }; state .store @@ -285,7 +285,7 @@ async fn get_or_update_dispute_object( connector_reason: dispute_details.connector_reason, connector_reason_code: dispute_details.connector_reason_code, challenge_required_by: dispute_details.challenge_required_by, - updated_at: dispute_details.updated_at, + connector_updated_at: dispute_details.updated_at, }; db.update_dispute(dispute, update_dispute) .await diff --git a/crates/router/src/db/dispute.rs b/crates/router/src/db/dispute.rs index 596ec20bb0..13dac2d0f4 100644 --- a/crates/router/src/db/dispute.rs +++ b/crates/router/src/db/dispute.rs @@ -33,6 +33,12 @@ pub trait DisputeInterface { dispute_constraints: api_models::disputes::DisputeListConstraints, ) -> CustomResult, errors::StorageError>; + async fn find_disputes_by_merchant_id_payment_id( + &self, + merchant_id: &str, + payment_id: &str, + ) -> CustomResult, errors::StorageError>; + async fn update_dispute( &self, this: storage::Dispute, @@ -96,6 +102,18 @@ impl DisputeInterface for Store { .into_report() } + async fn find_disputes_by_merchant_id_payment_id( + &self, + merchant_id: &str, + payment_id: &str, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + storage::Dispute::find_by_merchant_id_payment_id(&conn, merchant_id, payment_id) + .await + .map_err(Into::into) + .into_report() + } + async fn update_dispute( &self, this: storage::Dispute, @@ -146,6 +164,15 @@ impl DisputeInterface for MockDb { Err(errors::StorageError::MockDbError)? } + async fn find_disputes_by_merchant_id_payment_id( + &self, + _merchant_id: &str, + _payment_id: &str, + ) -> CustomResult, errors::StorageError> { + // TODO: Implement function for `MockDb` + Err(errors::StorageError::MockDbError)? + } + async fn update_dispute( &self, _this: storage::Dispute, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 2b7b393783..b347809668 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -500,9 +500,27 @@ impl ForeignFrom for api_models::disputes::DisputeResponse { connector_reason: dispute.connector_reason, connector_reason_code: dispute.connector_reason_code, challenge_required_by: dispute.challenge_required_by, - created_at: dispute.dispute_created_at, - updated_at: dispute.updated_at, - received_at: dispute.created_at.to_string(), + connector_created_at: dispute.connector_created_at, + connector_updated_at: dispute.connector_updated_at, + created_at: dispute.created_at, + } + } +} + +impl ForeignFrom for api_models::disputes::DisputeResponsePaymentsRetrieve { + fn foreign_from(dispute: storage::Dispute) -> Self { + Self { + dispute_id: dispute.dispute_id, + dispute_stage: dispute.dispute_stage.foreign_into(), + dispute_status: dispute.dispute_status.foreign_into(), + connector_status: dispute.connector_status, + connector_dispute_id: dispute.connector_dispute_id, + connector_reason: dispute.connector_reason, + connector_reason_code: dispute.connector_reason_code, + challenge_required_by: dispute.challenge_required_by, + connector_created_at: dispute.connector_created_at, + connector_updated_at: dispute.connector_updated_at, + created_at: dispute.created_at, } } } diff --git a/crates/storage_models/src/dispute.rs b/crates/storage_models/src/dispute.rs index bf9c514650..8ab879f944 100644 --- a/crates/storage_models/src/dispute.rs +++ b/crates/storage_models/src/dispute.rs @@ -22,8 +22,8 @@ pub struct DisputeNew { pub connector_reason: Option, pub connector_reason_code: Option, pub challenge_required_by: Option, - pub dispute_created_at: Option, - pub updated_at: Option, + pub connector_created_at: Option, + pub connector_updated_at: Option, pub connector: String, } @@ -45,8 +45,8 @@ pub struct Dispute { pub connector_reason: Option, pub connector_reason_code: Option, pub challenge_required_by: Option, - pub dispute_created_at: Option, - pub updated_at: Option, + pub connector_created_at: Option, + pub connector_updated_at: Option, #[serde(with = "custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "custom_serde::iso8601")] @@ -63,7 +63,7 @@ pub enum DisputeUpdate { connector_reason: Option, connector_reason_code: Option, challenge_required_by: Option, - updated_at: Option, + connector_updated_at: Option, }, StatusUpdate { dispute_status: storage_enums::DisputeStatus, @@ -80,7 +80,7 @@ pub struct DisputeUpdateInternal { connector_reason: Option, connector_reason_code: Option, challenge_required_by: Option, - updated_at: Option, + connector_updated_at: Option, modified_at: Option, } @@ -94,7 +94,7 @@ impl From for DisputeUpdateInternal { connector_reason, connector_reason_code, challenge_required_by, - updated_at, + connector_updated_at, } => Self { dispute_stage: Some(dispute_stage), dispute_status, @@ -102,7 +102,7 @@ impl From for DisputeUpdateInternal { connector_reason, connector_reason_code, challenge_required_by, - updated_at, + connector_updated_at, modified_at: Some(common_utils::date_time::now()), }, DisputeUpdate::StatusUpdate { diff --git a/crates/storage_models/src/query/dispute.rs b/crates/storage_models/src/query/dispute.rs index 5738e29798..78696d9e9c 100644 --- a/crates/storage_models/src/query/dispute.rs +++ b/crates/storage_models/src/query/dispute.rs @@ -1,4 +1,4 @@ -use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, Table}; use router_env::{instrument, tracing}; use super::generics; @@ -48,6 +48,28 @@ impl Dispute { .await } + pub async fn find_by_merchant_id_payment_id( + conn: &PgPooledConn, + merchant_id: &str, + payment_id: &str, + ) -> StorageResult> { + generics::generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::payment_id.eq(payment_id.to_owned())), + None, + None, + None, + ) + .await + } + #[instrument(skip(conn))] pub async fn update(self, conn: &PgPooledConn, dispute: DisputeUpdate) -> StorageResult { match generics::generic_update_with_unique_predicate_get_result::< diff --git a/crates/storage_models/src/schema.rs b/crates/storage_models/src/schema.rs index 2aadb280e1..cec6853450 100644 --- a/crates/storage_models/src/schema.rs +++ b/crates/storage_models/src/schema.rs @@ -130,8 +130,8 @@ diesel::table! { connector_reason -> Nullable, connector_reason_code -> Nullable, challenge_required_by -> Nullable, - dispute_created_at -> Nullable, - updated_at -> Nullable, + connector_created_at -> Nullable, + connector_updated_at -> Nullable, created_at -> Timestamp, modified_at -> Timestamp, connector -> Varchar, diff --git a/migrations/2023-05-08-141907_rename_dispute_cols/down.sql b/migrations/2023-05-08-141907_rename_dispute_cols/down.sql new file mode 100644 index 0000000000..4a6d69aed4 --- /dev/null +++ b/migrations/2023-05-08-141907_rename_dispute_cols/down.sql @@ -0,0 +1,5 @@ +ALTER TABLE dispute +RENAME COLUMN connector_created_at TO dispute_created_at; + +ALTER TABLE dispute +RENAME COLUMN connector_updated_at TO updated_at; diff --git a/migrations/2023-05-08-141907_rename_dispute_cols/up.sql b/migrations/2023-05-08-141907_rename_dispute_cols/up.sql new file mode 100644 index 0000000000..28e5d5b354 --- /dev/null +++ b/migrations/2023-05-08-141907_rename_dispute_cols/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +ALTER TABLE dispute +RENAME COLUMN dispute_created_at TO connector_created_at; + +ALTER TABLE dispute +RENAME COLUMN updated_at TO connector_updated_at;