diff --git a/crates/api_models/src/disputes.rs b/crates/api_models/src/disputes.rs index c9e9fd98fe..7629eeee87 100644 --- a/crates/api_models/src/disputes.rs +++ b/crates/api_models/src/disputes.rs @@ -3,6 +3,7 @@ use time::PrimitiveDateTime; use utoipa::ToSchema; use super::enums::{DisputeStage, DisputeStatus}; +use crate::files; #[derive(Clone, Debug, Serialize, ToSchema, Eq, PartialEq)] pub struct DisputeResponse { @@ -74,6 +75,30 @@ pub struct DisputeResponsePaymentsRetrieve { pub created_at: PrimitiveDateTime, } +#[derive(Debug, Serialize, strum::Display, Clone)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum EvidenceType { + CancellationPolicy, + CustomerCommunication, + CustomerSignature, + Receipt, + RefundPolicy, + ServiceDocumentation, + ShippingDocumentation, + InvoiceShowingDistinctTransactions, + RecurringTransactionAgreement, + UncategorizedFile, +} + +#[derive(Clone, Debug, Serialize, ToSchema)] +pub struct DisputeEvidenceBlock { + /// Evidence type + pub evidence_type: EvidenceType, + /// File metadata + pub file_metadata_response: files::FileMetadataResponse, +} + #[derive(Clone, Debug, Deserialize, ToSchema)] #[serde(deny_unknown_fields)] pub struct DisputeListConstraints { diff --git a/crates/api_models/src/files.rs b/crates/api_models/src/files.rs index 6fafce7864..b8cb8bd3d9 100644 --- a/crates/api_models/src/files.rs +++ b/crates/api_models/src/files.rs @@ -5,3 +5,17 @@ pub struct CreateFileResponse { /// ID of the file created pub file_id: String, } + +#[derive(Debug, serde::Serialize, ToSchema, Clone)] +pub struct FileMetadataResponse { + /// ID of the file created + pub file_id: String, + /// Name of the file + pub file_name: Option, + /// Size of the file + pub file_size: i32, + /// Type of the file + pub file_type: String, + /// File availability + pub available: bool, +} diff --git a/crates/router/src/core/disputes.rs b/crates/router/src/core/disputes.rs index 376846cbdb..50b88adc5d 100644 --- a/crates/router/src/core/disputes.rs +++ b/crates/router/src/core/disputes.rs @@ -378,3 +378,27 @@ pub async fn attach_evidence( })?; Ok(create_file_response) } + +#[instrument(skip(state))] +pub async fn retrieve_dispute_evidence( + state: &AppState, + merchant_account: storage::MerchantAccount, + req: disputes::DisputeId, +) -> RouterResponse> { + let dispute = state + .store + .find_dispute_by_merchant_id_dispute_id(&merchant_account.merchant_id, &req.dispute_id) + .await + .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound { + dispute_id: req.dispute_id, + })?; + let dispute_evidence: api::DisputeEvidence = dispute + .evidence + .clone() + .parse_value("DisputeEvidence") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while parsing dispute evidence record")?; + let dispute_evidence_vec = + transformers::get_dispute_evidence_vec(state, merchant_account, dispute_evidence).await?; + Ok(services::ApplicationResponse::Json(dispute_evidence_vec)) +} diff --git a/crates/router/src/core/disputes/transformers.rs b/crates/router/src/core/disputes/transformers.rs index 1f66266701..4cba43f3f4 100644 --- a/crates/router/src/core/disputes/transformers.rs +++ b/crates/router/src/core/disputes/transformers.rs @@ -1,10 +1,14 @@ +use api_models::disputes::EvidenceType; use common_utils::errors::CustomResult; +use error_stack::ResultExt; use crate::{ core::{errors, files::helpers::retrieve_file_and_provider_file_id_from_file_id}, routes::AppState, types::{ api::{self, DisputeEvidence}, + storage, + transformers::ForeignFrom, SubmitEvidenceRequestData, }, }; @@ -186,3 +190,146 @@ pub fn update_dispute_evidence( }, } } + +pub async fn get_dispute_evidence_block( + state: &AppState, + merchant_account: &storage::MerchantAccount, + evidence_type: EvidenceType, + file_id: String, +) -> CustomResult { + let file_metadata = state + .store + .find_file_metadata_by_merchant_id_file_id(&merchant_account.merchant_id, &file_id) + .await + .change_context(errors::ApiErrorResponse::FileNotFound) + .attach_printable("Unable to retrieve file_metadata")?; + let file_metadata_response = + api_models::files::FileMetadataResponse::foreign_from(file_metadata); + Ok(api_models::disputes::DisputeEvidenceBlock { + evidence_type, + file_metadata_response, + }) +} + +pub async fn get_dispute_evidence_vec( + state: &AppState, + merchant_account: storage::MerchantAccount, + dispute_evidence: DisputeEvidence, +) -> CustomResult, errors::ApiErrorResponse> { + let mut dispute_evidence_blocks: Vec = vec![]; + if let Some(cancellation_policy_block) = dispute_evidence.cancellation_policy { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::CancellationPolicy, + cancellation_policy_block, + ) + .await?, + ) + } + if let Some(customer_communication_block) = dispute_evidence.customer_communication { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::CustomerCommunication, + customer_communication_block, + ) + .await?, + ) + } + if let Some(customer_signature_block) = dispute_evidence.customer_signature { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::CustomerSignature, + customer_signature_block, + ) + .await?, + ) + } + if let Some(receipt_block) = dispute_evidence.receipt { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::Receipt, + receipt_block, + ) + .await?, + ) + } + if let Some(refund_policy_block) = dispute_evidence.refund_policy { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::RefundPolicy, + refund_policy_block, + ) + .await?, + ) + } + if let Some(service_documentation_block) = dispute_evidence.service_documentation { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::ServiceDocumentation, + service_documentation_block, + ) + .await?, + ) + } + if let Some(shipping_documentation_block) = dispute_evidence.shipping_documentation { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::ShippingDocumentation, + shipping_documentation_block, + ) + .await?, + ) + } + if let Some(invoice_showing_distinct_transactions_block) = + dispute_evidence.invoice_showing_distinct_transactions + { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::InvoiceShowingDistinctTransactions, + invoice_showing_distinct_transactions_block, + ) + .await?, + ) + } + if let Some(recurring_transaction_agreement_block) = + dispute_evidence.recurring_transaction_agreement + { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::RecurringTransactionAgreement, + recurring_transaction_agreement_block, + ) + .await?, + ) + } + if let Some(uncategorized_file_block) = dispute_evidence.uncategorized_file { + dispute_evidence_blocks.push( + get_dispute_evidence_block( + state, + &merchant_account, + EvidenceType::UncategorizedFile, + uncategorized_file_block, + ) + .await?, + ) + } + Ok(dispute_evidence_blocks) +} diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 74478c73ac..faa8eab994 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -430,6 +430,10 @@ impl Disputes { .route(web::post().to(submit_dispute_evidence)) .route(web::put().to(attach_dispute_evidence)), ) + .service( + web::resource("/evidence/{dispute_id}") + .route(web::get().to(retrieve_dispute_evidence)), + ) .service(web::resource("/{dispute_id}").route(web::get().to(retrieve_dispute))) } } diff --git a/crates/router/src/routes/disputes.rs b/crates/router/src/routes/disputes.rs index d3fc4523a0..9b81654a53 100644 --- a/crates/router/src/routes/disputes.rs +++ b/crates/router/src/routes/disputes.rs @@ -195,3 +195,39 @@ pub async fn attach_dispute_evidence( ) .await } + +/// Diputes - Retrieve Dispute +#[utoipa::path( + get, + path = "/disputes/evidence/{dispute_id}", + params( + ("dispute_id" = String, Path, description = "The identifier for dispute") + ), + responses( + (status = 200, description = "The dispute evidence was retrieved successfully", body = DisputeResponse), + (status = 404, description = "Dispute does not exist in our records") + ), + tag = "Disputes", + operation_id = "Retrieve a Dispute Evidence", + security(("api_key" = [])) +)] +#[instrument(skip_all, fields(flow = ?Flow::RetrieveDisputeEvidence))] +pub async fn retrieve_dispute_evidence( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::RetrieveDisputeEvidence; + let dispute_id = dispute_types::DisputeId { + dispute_id: path.into_inner(), + }; + api::server_wrap( + flow, + state.get_ref(), + &req, + dispute_id, + disputes::retrieve_dispute_evidence, + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + ) + .await +} diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 6965681fd5..7f5a7f1b55 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -526,6 +526,18 @@ impl ForeignFrom for api_models::disputes::DisputeResponsePaym } } +impl ForeignFrom for api_models::files::FileMetadataResponse { + fn foreign_from(file_metadata: storage::FileMetadata) -> Self { + Self { + file_id: file_metadata.file_id, + file_name: file_metadata.file_name, + file_size: file_metadata.file_size, + file_type: file_metadata.file_type, + available: file_metadata.available, + } + } +} + impl ForeignFrom for api_models::cards_info::CardInfoResponse { diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 56c91f4ae9..12ebb2e205 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -180,6 +180,8 @@ pub enum Flow { CreateConfigKey, /// Attach Dispute Evidence flow AttachDisputeEvidence, + /// Retrieve Dispute Evidence flow + RetrieveDisputeEvidence, } ///