mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
feat(router): add attach dispute evidence api (#1070)
Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
cc121d0feb
commit
a5756aaecf
@ -1,5 +1,6 @@
|
|||||||
use api_models::disputes as dispute_models;
|
use api_models::{disputes as dispute_models, files as files_api_models};
|
||||||
use error_stack::ResultExt;
|
use common_utils::ext_traits::ValueExt;
|
||||||
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use router_env::{instrument, tracing};
|
use router_env::{instrument, tracing};
|
||||||
pub mod transformers;
|
pub mod transformers;
|
||||||
|
|
||||||
@ -8,7 +9,7 @@ use super::{
|
|||||||
metrics,
|
metrics,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{payments, utils},
|
core::{files, payments, utils as core_utils},
|
||||||
routes::AppState,
|
routes::AppState,
|
||||||
services,
|
services,
|
||||||
types::{
|
types::{
|
||||||
@ -18,6 +19,7 @@ use crate::{
|
|||||||
AcceptDisputeRequestData, AcceptDisputeResponse, DefendDisputeRequestData,
|
AcceptDisputeRequestData, AcceptDisputeResponse, DefendDisputeRequestData,
|
||||||
DefendDisputeResponse, SubmitEvidenceRequestData, SubmitEvidenceResponse,
|
DefendDisputeResponse, SubmitEvidenceRequestData, SubmitEvidenceResponse,
|
||||||
},
|
},
|
||||||
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
@ -111,7 +113,7 @@ pub async fn accept_dispute(
|
|||||||
AcceptDisputeRequestData,
|
AcceptDisputeRequestData,
|
||||||
AcceptDisputeResponse,
|
AcceptDisputeResponse,
|
||||||
> = connector_data.connector.get_connector_integration();
|
> = connector_data.connector.get_connector_integration();
|
||||||
let router_data = utils::construct_accept_dispute_router_data(
|
let router_data = core_utils::construct_accept_dispute_router_data(
|
||||||
state,
|
state,
|
||||||
&payment_intent,
|
&payment_intent,
|
||||||
&payment_attempt,
|
&payment_attempt,
|
||||||
@ -150,7 +152,7 @@ pub async fn accept_dispute(
|
|||||||
.await
|
.await
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
.attach_printable_lazy(|| {
|
.attach_printable_lazy(|| {
|
||||||
format!("Unable to update dispute with dispute_id: {}", dispute_id)
|
format!("Unable to update dispute with dispute_id: {dispute_id}")
|
||||||
})?;
|
})?;
|
||||||
let dispute_response = api_models::disputes::DisputeResponse::foreign_from(updated_dispute);
|
let dispute_response = api_models::disputes::DisputeResponse::foreign_from(updated_dispute);
|
||||||
Ok(services::ApplicationResponse::Json(dispute_response))
|
Ok(services::ApplicationResponse::Json(dispute_response))
|
||||||
@ -217,7 +219,7 @@ pub async fn submit_evidence(
|
|||||||
SubmitEvidenceRequestData,
|
SubmitEvidenceRequestData,
|
||||||
SubmitEvidenceResponse,
|
SubmitEvidenceResponse,
|
||||||
> = connector_data.connector.get_connector_integration();
|
> = connector_data.connector.get_connector_integration();
|
||||||
let router_data = utils::construct_submit_evidence_router_data(
|
let router_data = core_utils::construct_submit_evidence_router_data(
|
||||||
state,
|
state,
|
||||||
&payment_intent,
|
&payment_intent,
|
||||||
&payment_attempt,
|
&payment_attempt,
|
||||||
@ -254,7 +256,7 @@ pub async fn submit_evidence(
|
|||||||
DefendDisputeRequestData,
|
DefendDisputeRequestData,
|
||||||
DefendDisputeResponse,
|
DefendDisputeResponse,
|
||||||
> = connector_data.connector.get_connector_integration();
|
> = connector_data.connector.get_connector_integration();
|
||||||
let defend_dispute_router_data = utils::construct_defend_dispute_router_data(
|
let defend_dispute_router_data = core_utils::construct_defend_dispute_router_data(
|
||||||
state,
|
state,
|
||||||
&payment_intent,
|
&payment_intent,
|
||||||
&payment_attempt,
|
&payment_attempt,
|
||||||
@ -299,8 +301,80 @@ pub async fn submit_evidence(
|
|||||||
.await
|
.await
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
.attach_printable_lazy(|| {
|
.attach_printable_lazy(|| {
|
||||||
format!("Unable to update dispute with dispute_id: {}", dispute_id)
|
format!("Unable to update dispute with dispute_id: {dispute_id}")
|
||||||
})?;
|
})?;
|
||||||
let dispute_response = api_models::disputes::DisputeResponse::foreign_from(updated_dispute);
|
let dispute_response = api_models::disputes::DisputeResponse::foreign_from(updated_dispute);
|
||||||
Ok(services::ApplicationResponse::Json(dispute_response))
|
Ok(services::ApplicationResponse::Json(dispute_response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn attach_evidence(
|
||||||
|
state: &AppState,
|
||||||
|
merchant_account: storage::MerchantAccount,
|
||||||
|
attach_evidence_request: api::AttachEvidenceRequest,
|
||||||
|
) -> RouterResponse<files_api_models::CreateFileResponse> {
|
||||||
|
let db = &state.store;
|
||||||
|
let dispute_id = attach_evidence_request
|
||||||
|
.create_file_request
|
||||||
|
.dispute_id
|
||||||
|
.clone()
|
||||||
|
.ok_or(errors::ApiErrorResponse::MissingDisputeId)?;
|
||||||
|
let dispute = db
|
||||||
|
.find_dispute_by_merchant_id_dispute_id(&merchant_account.merchant_id, &dispute_id)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
|
||||||
|
dispute_id: dispute_id.clone(),
|
||||||
|
})?;
|
||||||
|
common_utils::fp_utils::when(
|
||||||
|
!(dispute.dispute_stage == storage_enums::DisputeStage::Dispute
|
||||||
|
&& dispute.dispute_status == storage_enums::DisputeStatus::DisputeOpened),
|
||||||
|
|| {
|
||||||
|
metrics::ATTACH_EVIDENCE_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(
|
||||||
|
&metrics::CONTEXT,
|
||||||
|
1,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
Err(errors::ApiErrorResponse::DisputeStatusValidationFailed {
|
||||||
|
reason: format!(
|
||||||
|
"Evidence cannot be attached because the dispute is in {} stage and has {} status",
|
||||||
|
dispute.dispute_stage, dispute.dispute_status
|
||||||
|
),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
let create_file_response = files::files_create_core(
|
||||||
|
state,
|
||||||
|
merchant_account,
|
||||||
|
attach_evidence_request.create_file_request,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let file_id = match &create_file_response {
|
||||||
|
services::ApplicationResponse::Json(res) => res.file_id.clone(),
|
||||||
|
_ => Err(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.into_report()
|
||||||
|
.attach_printable("Unexpected response received from files create core")?,
|
||||||
|
};
|
||||||
|
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 updated_dispute_evidence = transformers::update_dispute_evidence(
|
||||||
|
dispute_evidence,
|
||||||
|
attach_evidence_request.evidence_type,
|
||||||
|
file_id,
|
||||||
|
);
|
||||||
|
let update_dispute = storage_models::dispute::DisputeUpdate::EvidenceUpdate {
|
||||||
|
evidence: utils::Encode::<api::DisputeEvidence>::encode_to_value(&updated_dispute_evidence)
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("Error while encoding dispute evidence")?
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
db.update_dispute(dispute, update_dispute)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable_lazy(|| {
|
||||||
|
format!("Unable to update dispute with dispute_id: {dispute_id}")
|
||||||
|
})?;
|
||||||
|
Ok(create_file_response)
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,10 @@ use common_utils::errors::CustomResult;
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::{errors, files::helpers::retrieve_file_and_provider_file_id_from_file_id},
|
core::{errors, files::helpers::retrieve_file_and_provider_file_id_from_file_id},
|
||||||
routes::AppState,
|
routes::AppState,
|
||||||
types::{api, SubmitEvidenceRequestData},
|
types::{
|
||||||
|
api::{self, DisputeEvidence},
|
||||||
|
SubmitEvidenceRequestData,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn get_evidence_request_data(
|
pub async fn get_evidence_request_data(
|
||||||
@ -134,3 +137,52 @@ pub async fn get_evidence_request_data(
|
|||||||
uncategorized_text: evidence_request.uncategorized_text,
|
uncategorized_text: evidence_request.uncategorized_text,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_dispute_evidence(
|
||||||
|
dispute_evidence: DisputeEvidence,
|
||||||
|
evidence_type: api::EvidenceType,
|
||||||
|
file_id: String,
|
||||||
|
) -> DisputeEvidence {
|
||||||
|
match evidence_type {
|
||||||
|
api::EvidenceType::CancellationPolicy => DisputeEvidence {
|
||||||
|
cancellation_policy: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::CustomerCommunication => DisputeEvidence {
|
||||||
|
customer_communication: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::CustomerSignature => DisputeEvidence {
|
||||||
|
customer_signature: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::Receipt => DisputeEvidence {
|
||||||
|
receipt: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::RefundPolicy => DisputeEvidence {
|
||||||
|
refund_policy: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::ServiceDocumentation => DisputeEvidence {
|
||||||
|
service_documentation: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::ShippingDocumentation => DisputeEvidence {
|
||||||
|
shipping_documentation: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::InvoiceShowingDistinctTransactions => DisputeEvidence {
|
||||||
|
invoice_showing_distinct_transactions: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::RecurringTransactionAgreement => DisputeEvidence {
|
||||||
|
recurring_transaction_agreement: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
api::EvidenceType::UncategorizedFile => DisputeEvidence {
|
||||||
|
uncategorized_file: Some(file_id),
|
||||||
|
..dispute_evidence
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -21,8 +21,13 @@ counter_metric!(
|
|||||||
counter_metric!(
|
counter_metric!(
|
||||||
ACCEPT_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC,
|
ACCEPT_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC,
|
||||||
GLOBAL_METER
|
GLOBAL_METER
|
||||||
); //No. of status validation fialures while accpeting a dispute
|
); //No. of status validation failures while accpeting a dispute
|
||||||
counter_metric!(
|
counter_metric!(
|
||||||
EVIDENCE_SUBMISSION_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC,
|
EVIDENCE_SUBMISSION_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC,
|
||||||
GLOBAL_METER
|
GLOBAL_METER
|
||||||
); //No. of status validation fialures while submitting evidence for a dispute
|
); //No. of status validation failures while submitting evidence for a dispute
|
||||||
|
//No. of status validation failures while attaching evidence for a dispute
|
||||||
|
counter_metric!(
|
||||||
|
ATTACH_EVIDENCE_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC,
|
||||||
|
GLOBAL_METER
|
||||||
|
);
|
||||||
|
|||||||
@ -258,6 +258,7 @@ async fn get_or_update_dispute_object(
|
|||||||
challenge_required_by: dispute_details.challenge_required_by,
|
challenge_required_by: dispute_details.challenge_required_by,
|
||||||
connector_created_at: dispute_details.created_at,
|
connector_created_at: dispute_details.created_at,
|
||||||
connector_updated_at: dispute_details.updated_at,
|
connector_updated_at: dispute_details.updated_at,
|
||||||
|
evidence: None,
|
||||||
};
|
};
|
||||||
state
|
state
|
||||||
.store
|
.store
|
||||||
|
|||||||
@ -424,7 +424,11 @@ impl Disputes {
|
|||||||
.app_data(web::Data::new(state))
|
.app_data(web::Data::new(state))
|
||||||
.service(web::resource("/list").route(web::get().to(retrieve_disputes_list)))
|
.service(web::resource("/list").route(web::get().to(retrieve_disputes_list)))
|
||||||
.service(web::resource("/accept/{dispute_id}").route(web::post().to(accept_dispute)))
|
.service(web::resource("/accept/{dispute_id}").route(web::post().to(accept_dispute)))
|
||||||
.service(web::resource("/evidence").route(web::post().to(submit_dispute_evidence)))
|
.service(
|
||||||
|
web::resource("/evidence")
|
||||||
|
.route(web::post().to(submit_dispute_evidence))
|
||||||
|
.route(web::put().to(attach_dispute_evidence)),
|
||||||
|
)
|
||||||
.service(web::resource("/{dispute_id}").route(web::get().to(retrieve_dispute)))
|
.service(web::resource("/{dispute_id}").route(web::get().to(retrieve_dispute)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
|
use actix_multipart::Multipart;
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use api_models::disputes as dispute_models;
|
use api_models::disputes as dispute_models;
|
||||||
use router_env::{instrument, tracing, Flow};
|
use router_env::{instrument, tracing, Flow};
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
use super::app::AppState;
|
use super::app::AppState;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::disputes,
|
core::disputes,
|
||||||
services::{api, authentication as auth},
|
services::{api, authentication as auth},
|
||||||
types::api::disputes::{self as dispute_types},
|
types::api::disputes as dispute_types,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Diputes - Retrieve Dispute
|
/// Diputes - Retrieve Dispute
|
||||||
@ -154,3 +156,42 @@ pub async fn submit_dispute_evidence(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disputes - Attach Evidence to Dispute
|
||||||
|
///
|
||||||
|
/// To attach an evidence file to dispute
|
||||||
|
#[utoipa::path(
|
||||||
|
put,
|
||||||
|
path = "/disputes/evidence",
|
||||||
|
request_body=MultipartRequestWithFile,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Evidence attached to dispute", body = CreateFileResponse),
|
||||||
|
(status = 400, description = "Bad Request")
|
||||||
|
),
|
||||||
|
tag = "Disputes",
|
||||||
|
operation_id = "Attach Evidence to Dispute",
|
||||||
|
security(("api_key" = []))
|
||||||
|
)]
|
||||||
|
#[instrument(skip_all, fields(flow = ?Flow::AttachDisputeEvidence))]
|
||||||
|
pub async fn attach_dispute_evidence(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: Multipart,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::AttachDisputeEvidence;
|
||||||
|
//Get attach_evidence_request from the multipart request
|
||||||
|
let attach_evidence_request_result = utils::get_attach_evidence_request(payload).await;
|
||||||
|
let attach_evidence_request = match attach_evidence_request_result {
|
||||||
|
Ok(valid_request) => valid_request,
|
||||||
|
Err(err) => return api::log_and_return_error_response(err),
|
||||||
|
};
|
||||||
|
api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state.get_ref(),
|
||||||
|
&req,
|
||||||
|
attach_evidence_request,
|
||||||
|
disputes::attach_evidence,
|
||||||
|
auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|||||||
102
crates/router/src/routes/disputes/utils.rs
Normal file
102
crates/router/src/routes/disputes/utils.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use actix_multipart::{Field, Multipart};
|
||||||
|
use actix_web::web::Bytes;
|
||||||
|
use common_utils::{errors::CustomResult, ext_traits::StringExt, fp_utils};
|
||||||
|
use error_stack::{IntoReport, ResultExt};
|
||||||
|
use futures::{StreamExt, TryStreamExt};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
core::{errors, files::helpers},
|
||||||
|
types::api::{disputes, files},
|
||||||
|
utils::OptionExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn parse_evidence_type(
|
||||||
|
field: &mut Field,
|
||||||
|
) -> CustomResult<Option<disputes::EvidenceType>, errors::ApiErrorResponse> {
|
||||||
|
let purpose = helpers::read_string(field).await;
|
||||||
|
match purpose {
|
||||||
|
Some(evidence_type) => Ok(Some(
|
||||||
|
evidence_type
|
||||||
|
.parse_enum("Evidence Type")
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("Error parsing evidence type")?,
|
||||||
|
)),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_attach_evidence_request(
|
||||||
|
mut payload: Multipart,
|
||||||
|
) -> CustomResult<disputes::AttachEvidenceRequest, errors::ApiErrorResponse> {
|
||||||
|
let mut option_evidence_type: Option<disputes::EvidenceType> = None;
|
||||||
|
let mut dispute_id: Option<String> = None;
|
||||||
|
|
||||||
|
let mut file_name: Option<String> = None;
|
||||||
|
let mut file_content: Option<Vec<Bytes>> = None;
|
||||||
|
|
||||||
|
while let Ok(Some(mut field)) = payload.try_next().await {
|
||||||
|
let content_disposition = field.content_disposition();
|
||||||
|
let field_name = content_disposition.get_name();
|
||||||
|
// Parse the different parameters expected in the multipart request
|
||||||
|
match field_name {
|
||||||
|
Some("file") => {
|
||||||
|
file_name = content_disposition.get_filename().map(String::from);
|
||||||
|
//Collect the file content and throw error if something fails
|
||||||
|
let mut file_data = Vec::new();
|
||||||
|
let mut stream = field.into_stream();
|
||||||
|
while let Some(chunk) = stream.next().await {
|
||||||
|
match chunk {
|
||||||
|
Ok(bytes) => file_data.push(bytes),
|
||||||
|
Err(err) => Err(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.into_report()
|
||||||
|
.attach_printable_lazy(|| format!("File parsing error: {err}"))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_content = Some(file_data)
|
||||||
|
}
|
||||||
|
Some("dispute_id") => {
|
||||||
|
dispute_id = helpers::read_string(&mut field).await;
|
||||||
|
}
|
||||||
|
Some("evidence_type") => {
|
||||||
|
option_evidence_type = parse_evidence_type(&mut field).await?;
|
||||||
|
}
|
||||||
|
// Can ignore other params
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let evidence_type = option_evidence_type.get_required_value("evidence_type")?;
|
||||||
|
let file = file_content.get_required_value("file")?.concat().to_vec();
|
||||||
|
//Get and validate file size
|
||||||
|
let file_size: i32 = file
|
||||||
|
.len()
|
||||||
|
.try_into()
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("File size error")?;
|
||||||
|
// Check if empty file and throw error
|
||||||
|
fp_utils::when(file_size <= 0, || {
|
||||||
|
Err(errors::ApiErrorResponse::MissingFile)
|
||||||
|
.into_report()
|
||||||
|
.attach_printable("Missing / Invalid file in the request")
|
||||||
|
})?;
|
||||||
|
// Get file mime type using 'infer'
|
||||||
|
let kind = infer::get(&file).ok_or(errors::ApiErrorResponse::MissingFileContentType)?;
|
||||||
|
let file_type = kind
|
||||||
|
.mime_type()
|
||||||
|
.parse::<mime::Mime>()
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ApiErrorResponse::MissingFileContentType)
|
||||||
|
.attach_printable("File content type error")?;
|
||||||
|
let create_file_request = files::CreateFileRequest {
|
||||||
|
file,
|
||||||
|
file_name,
|
||||||
|
file_size,
|
||||||
|
file_type,
|
||||||
|
purpose: files::FilePurpose::DisputeEvidence,
|
||||||
|
dispute_id,
|
||||||
|
};
|
||||||
|
Ok(disputes::AttachEvidenceRequest {
|
||||||
|
evidence_type,
|
||||||
|
create_file_request,
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -22,6 +22,42 @@ pub struct DisputePayload {
|
|||||||
pub updated_at: Option<PrimitiveDateTime>,
|
pub updated_at: Option<PrimitiveDateTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct DisputeEvidence {
|
||||||
|
pub cancellation_policy: Option<String>,
|
||||||
|
pub customer_communication: Option<String>,
|
||||||
|
pub customer_signature: Option<String>,
|
||||||
|
pub receipt: Option<String>,
|
||||||
|
pub refund_policy: Option<String>,
|
||||||
|
pub service_documentation: Option<String>,
|
||||||
|
pub shipping_documentation: Option<String>,
|
||||||
|
pub invoice_showing_distinct_transactions: Option<String>,
|
||||||
|
pub recurring_transaction_agreement: Option<String>,
|
||||||
|
pub uncategorized_file: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AttachEvidenceRequest {
|
||||||
|
pub create_file_request: types::api::CreateFileRequest,
|
||||||
|
pub evidence_type: EvidenceType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, strum::Display, strum::EnumString, 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(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Accept;
|
pub struct Accept;
|
||||||
|
|
||||||
|
|||||||
@ -176,6 +176,8 @@ pub enum Flow {
|
|||||||
RetrieveFile,
|
RetrieveFile,
|
||||||
/// Dispute Evidence submission flow
|
/// Dispute Evidence submission flow
|
||||||
DisputesEvidenceSubmit,
|
DisputesEvidenceSubmit,
|
||||||
|
/// Attach Dispute Evidence flow
|
||||||
|
AttachDisputeEvidence,
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use common_utils::custom_serde;
|
use common_utils::custom_serde;
|
||||||
use diesel::{AsChangeset, Identifiable, Insertable, Queryable};
|
use diesel::{AsChangeset, Identifiable, Insertable, Queryable};
|
||||||
|
use masking::Secret;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use time::PrimitiveDateTime;
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ pub struct DisputeNew {
|
|||||||
pub connector_created_at: Option<PrimitiveDateTime>,
|
pub connector_created_at: Option<PrimitiveDateTime>,
|
||||||
pub connector_updated_at: Option<PrimitiveDateTime>,
|
pub connector_updated_at: Option<PrimitiveDateTime>,
|
||||||
pub connector: String,
|
pub connector: String,
|
||||||
|
pub evidence: Option<Secret<serde_json::Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Identifiable, Queryable)]
|
#[derive(Clone, Debug, Serialize, Identifiable, Queryable)]
|
||||||
@ -52,6 +54,7 @@ pub struct Dispute {
|
|||||||
#[serde(with = "custom_serde::iso8601")]
|
#[serde(with = "custom_serde::iso8601")]
|
||||||
pub modified_at: PrimitiveDateTime,
|
pub modified_at: PrimitiveDateTime,
|
||||||
pub connector: String,
|
pub connector: String,
|
||||||
|
pub evidence: Secret<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -69,19 +72,23 @@ pub enum DisputeUpdate {
|
|||||||
dispute_status: storage_enums::DisputeStatus,
|
dispute_status: storage_enums::DisputeStatus,
|
||||||
connector_status: Option<String>,
|
connector_status: Option<String>,
|
||||||
},
|
},
|
||||||
|
EvidenceUpdate {
|
||||||
|
evidence: Secret<serde_json::Value>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
|
#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
|
||||||
#[diesel(table_name = dispute)]
|
#[diesel(table_name = dispute)]
|
||||||
pub struct DisputeUpdateInternal {
|
pub struct DisputeUpdateInternal {
|
||||||
dispute_stage: Option<storage_enums::DisputeStage>,
|
dispute_stage: Option<storage_enums::DisputeStage>,
|
||||||
dispute_status: storage_enums::DisputeStatus,
|
dispute_status: Option<storage_enums::DisputeStatus>,
|
||||||
connector_status: Option<String>,
|
connector_status: Option<String>,
|
||||||
connector_reason: Option<String>,
|
connector_reason: Option<String>,
|
||||||
connector_reason_code: Option<String>,
|
connector_reason_code: Option<String>,
|
||||||
challenge_required_by: Option<PrimitiveDateTime>,
|
challenge_required_by: Option<PrimitiveDateTime>,
|
||||||
connector_updated_at: Option<PrimitiveDateTime>,
|
connector_updated_at: Option<PrimitiveDateTime>,
|
||||||
modified_at: Option<PrimitiveDateTime>,
|
modified_at: Option<PrimitiveDateTime>,
|
||||||
|
evidence: Option<Secret<serde_json::Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DisputeUpdate> for DisputeUpdateInternal {
|
impl From<DisputeUpdate> for DisputeUpdateInternal {
|
||||||
@ -97,23 +104,28 @@ impl From<DisputeUpdate> for DisputeUpdateInternal {
|
|||||||
connector_updated_at,
|
connector_updated_at,
|
||||||
} => Self {
|
} => Self {
|
||||||
dispute_stage: Some(dispute_stage),
|
dispute_stage: Some(dispute_stage),
|
||||||
dispute_status,
|
dispute_status: Some(dispute_status),
|
||||||
connector_status: Some(connector_status),
|
connector_status: Some(connector_status),
|
||||||
connector_reason,
|
connector_reason,
|
||||||
connector_reason_code,
|
connector_reason_code,
|
||||||
challenge_required_by,
|
challenge_required_by,
|
||||||
connector_updated_at,
|
connector_updated_at,
|
||||||
modified_at: Some(common_utils::date_time::now()),
|
modified_at: Some(common_utils::date_time::now()),
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
DisputeUpdate::StatusUpdate {
|
DisputeUpdate::StatusUpdate {
|
||||||
dispute_status,
|
dispute_status,
|
||||||
connector_status,
|
connector_status,
|
||||||
} => Self {
|
} => Self {
|
||||||
dispute_status,
|
dispute_status: Some(dispute_status),
|
||||||
connector_status,
|
connector_status,
|
||||||
modified_at: Some(common_utils::date_time::now()),
|
modified_at: Some(common_utils::date_time::now()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
DisputeUpdate::EvidenceUpdate { evidence } => Self {
|
||||||
|
evidence: Some(evidence),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,6 +135,7 @@ diesel::table! {
|
|||||||
created_at -> Timestamp,
|
created_at -> Timestamp,
|
||||||
modified_at -> Timestamp,
|
modified_at -> Timestamp,
|
||||||
connector -> Varchar,
|
connector -> Varchar,
|
||||||
|
evidence -> Jsonb,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE dispute DROP COLUMN evidence;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE dispute
|
||||||
|
ADD COLUMN evidence JSONB NOT NULL DEFAULT '{}'::JSONB;
|
||||||
Reference in New Issue
Block a user