mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(router): add disputes block in payments retrieve response (#1038)
This commit is contained in:
committed by
GitHub
parent
7c7185bc1a
commit
1304d912e5
@ -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<PrimitiveDateTime>,
|
||||
/// Dispute created time sent by connector
|
||||
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
||||
pub created_at: Option<PrimitiveDateTime>,
|
||||
pub connector_created_at: Option<PrimitiveDateTime>,
|
||||
/// Dispute updated time sent by connector
|
||||
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
||||
pub updated_at: Option<PrimitiveDateTime>,
|
||||
pub connector_updated_at: Option<PrimitiveDateTime>,
|
||||
/// 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<String>,
|
||||
/// Reason code of dispute sent by connector
|
||||
pub connector_reason_code: Option<String>,
|
||||
/// Evidence deadline of dispute sent by connector
|
||||
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
||||
pub challenge_required_by: Option<PrimitiveDateTime>,
|
||||
/// Dispute created time sent by connector
|
||||
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
||||
pub connector_created_at: Option<PrimitiveDateTime>,
|
||||
/// Dispute updated time sent by connector
|
||||
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
||||
pub connector_updated_at: Option<PrimitiveDateTime>,
|
||||
/// Time at which dispute is received
|
||||
#[serde(with = "common_utils::custom_serde::iso8601")]
|
||||
pub created_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, ToSchema)]
|
||||
|
||||
@ -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<Vec<RefundResponse>>)]
|
||||
pub refunds: Option<Vec<refunds::RefundResponse>>,
|
||||
|
||||
/// List of dispute that happened on this intent
|
||||
#[schema(value_type = Option<Vec<DisputeResponsePaymentsRetrieve>>)]
|
||||
pub disputes: Option<Vec<disputes::DisputeResponsePaymentsRetrieve>>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
@ -860,6 +860,7 @@ where
|
||||
pub force_sync: Option<bool>,
|
||||
pub payment_method_data: Option<api::PaymentMethodData>,
|
||||
pub refunds: Vec<storage::Refund>,
|
||||
pub disputes: Vec<storage::Dispute>,
|
||||
pub sessions_token: Vec<api::SessionToken>,
|
||||
pub card_cvc: Option<Secret<String>>,
|
||||
pub email: Option<Email>,
|
||||
|
||||
@ -144,6 +144,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest>
|
||||
payment_method_data: None,
|
||||
force_sync: None,
|
||||
refunds: vec![],
|
||||
disputes: vec![],
|
||||
connector_response,
|
||||
sessions_token: vec![],
|
||||
card_cvc: None,
|
||||
|
||||
@ -150,6 +150,7 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
|
||||
confirm: None,
|
||||
payment_method_data: None,
|
||||
refunds: vec![],
|
||||
disputes: vec![],
|
||||
connector_response,
|
||||
sessions_token: vec![],
|
||||
card_cvc: None,
|
||||
|
||||
@ -188,6 +188,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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,
|
||||
|
||||
@ -207,6 +207,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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,
|
||||
|
||||
@ -230,6 +230,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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![],
|
||||
|
||||
@ -172,6 +172,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
|
||||
address: types::PaymentAddress::default(),
|
||||
force_sync: None,
|
||||
refunds: vec![],
|
||||
disputes: vec![],
|
||||
sessions_token: vec![],
|
||||
card_cvc: None,
|
||||
creds_identifier,
|
||||
|
||||
@ -164,6 +164,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
|
||||
payment_method_data: None,
|
||||
force_sync: None,
|
||||
refunds: vec![],
|
||||
disputes: vec![],
|
||||
sessions_token: vec![],
|
||||
connector_response,
|
||||
card_cvc: None,
|
||||
|
||||
@ -136,6 +136,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
|
||||
payment_method_data: None,
|
||||
force_sync: None,
|
||||
refunds: vec![],
|
||||
disputes: vec![],
|
||||
sessions_token: vec![],
|
||||
card_cvc: None,
|
||||
creds_identifier: None,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -287,6 +287,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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(),
|
||||
|
||||
@ -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<R, Op>(
|
||||
payment_attempt: storage::PaymentAttempt,
|
||||
payment_intent: storage::PaymentIntent,
|
||||
refunds: Vec<storage::Refund>,
|
||||
disputes: Vec<storage::Dispute>,
|
||||
payment_method_data: Option<api::PaymentMethodData>,
|
||||
customer: Option<storage::Customer>,
|
||||
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),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -33,6 +33,12 @@ pub trait DisputeInterface {
|
||||
dispute_constraints: api_models::disputes::DisputeListConstraints,
|
||||
) -> CustomResult<Vec<storage::Dispute>, errors::StorageError>;
|
||||
|
||||
async fn find_disputes_by_merchant_id_payment_id(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
payment_id: &str,
|
||||
) -> CustomResult<Vec<storage::Dispute>, 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<Vec<storage::Dispute>, 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<Vec<storage::Dispute>, errors::StorageError> {
|
||||
// TODO: Implement function for `MockDb`
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
|
||||
async fn update_dispute(
|
||||
&self,
|
||||
_this: storage::Dispute,
|
||||
|
||||
@ -500,9 +500,27 @@ impl ForeignFrom<storage::Dispute> 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<storage::Dispute> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,8 +22,8 @@ pub struct DisputeNew {
|
||||
pub connector_reason: Option<String>,
|
||||
pub connector_reason_code: Option<String>,
|
||||
pub challenge_required_by: Option<PrimitiveDateTime>,
|
||||
pub dispute_created_at: Option<PrimitiveDateTime>,
|
||||
pub updated_at: Option<PrimitiveDateTime>,
|
||||
pub connector_created_at: Option<PrimitiveDateTime>,
|
||||
pub connector_updated_at: Option<PrimitiveDateTime>,
|
||||
pub connector: String,
|
||||
}
|
||||
|
||||
@ -45,8 +45,8 @@ pub struct Dispute {
|
||||
pub connector_reason: Option<String>,
|
||||
pub connector_reason_code: Option<String>,
|
||||
pub challenge_required_by: Option<PrimitiveDateTime>,
|
||||
pub dispute_created_at: Option<PrimitiveDateTime>,
|
||||
pub updated_at: Option<PrimitiveDateTime>,
|
||||
pub connector_created_at: Option<PrimitiveDateTime>,
|
||||
pub connector_updated_at: Option<PrimitiveDateTime>,
|
||||
#[serde(with = "custom_serde::iso8601")]
|
||||
pub created_at: PrimitiveDateTime,
|
||||
#[serde(with = "custom_serde::iso8601")]
|
||||
@ -63,7 +63,7 @@ pub enum DisputeUpdate {
|
||||
connector_reason: Option<String>,
|
||||
connector_reason_code: Option<String>,
|
||||
challenge_required_by: Option<PrimitiveDateTime>,
|
||||
updated_at: Option<PrimitiveDateTime>,
|
||||
connector_updated_at: Option<PrimitiveDateTime>,
|
||||
},
|
||||
StatusUpdate {
|
||||
dispute_status: storage_enums::DisputeStatus,
|
||||
@ -80,7 +80,7 @@ pub struct DisputeUpdateInternal {
|
||||
connector_reason: Option<String>,
|
||||
connector_reason_code: Option<String>,
|
||||
challenge_required_by: Option<PrimitiveDateTime>,
|
||||
updated_at: Option<PrimitiveDateTime>,
|
||||
connector_updated_at: Option<PrimitiveDateTime>,
|
||||
modified_at: Option<PrimitiveDateTime>,
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ impl From<DisputeUpdate> 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<DisputeUpdate> 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 {
|
||||
|
||||
@ -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<Vec<Self>> {
|
||||
generics::generic_filter::<
|
||||
<Self as HasTable>::Table,
|
||||
_,
|
||||
<<Self as HasTable>::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<Self> {
|
||||
match generics::generic_update_with_unique_predicate_get_result::<
|
||||
|
||||
@ -130,8 +130,8 @@ diesel::table! {
|
||||
connector_reason -> Nullable<Varchar>,
|
||||
connector_reason_code -> Nullable<Varchar>,
|
||||
challenge_required_by -> Nullable<Timestamp>,
|
||||
dispute_created_at -> Nullable<Timestamp>,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
connector_created_at -> Nullable<Timestamp>,
|
||||
connector_updated_at -> Nullable<Timestamp>,
|
||||
created_at -> Timestamp,
|
||||
modified_at -> Timestamp,
|
||||
connector -> Varchar,
|
||||
|
||||
@ -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;
|
||||
6
migrations/2023-05-08-141907_rename_dispute_cols/up.sql
Normal file
6
migrations/2023-05-08-141907_rename_dispute_cols/up.sql
Normal file
@ -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;
|
||||
Reference in New Issue
Block a user