feat(router): add disputes block in payments retrieve response (#1038)

This commit is contained in:
Sai Harsha Vardhan
2023-05-09 23:57:07 +05:30
committed by GitHub
parent 7c7185bc1a
commit 1304d912e5
22 changed files with 168 additions and 22 deletions

View File

@ -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)]

View File

@ -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>,

View File

@ -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>,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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![],

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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(),

View File

@ -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),

View File

@ -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

View File

@ -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,

View File

@ -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,
}
}
}

View File

@ -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 {

View File

@ -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::<

View File

@ -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,

View File

@ -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;

View 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;