fix(router): [worldpayvantiv] dispute validations and statuses (#8862)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
AkshayaFoiger
2025-08-07 13:29:28 +05:30
committed by GitHub
parent 4a8eea9aa5
commit 1b2a98ce38
4 changed files with 147 additions and 40 deletions

View File

@ -838,6 +838,3 @@ click_to_pay = {connector_list = "adyen, cybersource, trustpay"}
[revenue_recovery] [revenue_recovery]
monitoring_threshold_in_seconds = 60 monitoring_threshold_in_seconds = 60
retry_algorithm_type = "cascading" retry_algorithm_type = "cascading"
[list_dispute_supported_connectors]
connector_list = "worldpayvantiv"

View File

@ -3856,7 +3856,60 @@ fn get_dispute_stage(
pub fn get_dispute_status( pub fn get_dispute_status(
dispute_cycle: String, dispute_cycle: String,
) -> Result<api_models::enums::DisputeStatus, error_stack::Report<errors::ConnectorError>> { dispute_activities: Vec<Activity>,
) -> Result<common_enums::DisputeStatus, error_stack::Report<errors::ConnectorError>> {
if let Some(activity) = get_last_non_auxiliary_activity_type(dispute_activities) {
match activity.as_ref() {
"Merchant Accept"
| "Issuer Accepted Pre-Arbitration"
| "Vantiv Accept"
| "Sent Credit" => Ok(common_enums::DisputeStatus::DisputeAccepted),
"Merchant Represent"
| "Respond to Dispute"
| "Respond to PreArb"
| "Request Arbitration"
| "Request Pre-Arbitration"
| "Create Arbitration"
| "Record Arbitration"
| "Create Pre-Arbitration"
| "File Arbitration"
| "File Pre-Arbitration"
| "File Visa Pre-Arbitration"
| "Send Representment"
| "Send Response"
| "Arbitration"
| "Arbitration (Mastercard)"
| "Arbitration Chargeback"
| "Issuer Declined Pre-Arbitration"
| "Issuer Arbitration"
| "Request Response to Pre-Arbitration"
| "Vantiv Represent"
| "Vantiv Respond"
| "Auto Represent"
| "Arbitration Ruling" => Ok(common_enums::DisputeStatus::DisputeChallenged),
"Arbitration Lost" | "Unsuccessful Arbitration" | "Unsuccessful Pre-Arbitration" => {
Ok(common_enums::DisputeStatus::DisputeLost)
}
"Arbitration Won"
| "Arbitration Split"
| "Successful Arbitration"
| "Successful Pre-Arbitration" => Ok(common_enums::DisputeStatus::DisputeWon),
"Chargeback Reversal" => Ok(common_enums::DisputeStatus::DisputeCancelled),
"Receive Network Transaction" => Ok(common_enums::DisputeStatus::DisputeOpened),
"Unaccept" | "Unrepresent" => Ok(common_enums::DisputeStatus::DisputeOpened),
unexpected_activity => Err(errors::ConnectorError::UnexpectedResponseError(
bytes::Bytes::from(format!("Dispute Activity: {unexpected_activity})")),
)
.into()),
}
} else {
match connector_utils::normalize_string(dispute_cycle.clone()) match connector_utils::normalize_string(dispute_cycle.clone())
.change_context(errors::ConnectorError::RequestEncodingFailed)? .change_context(errors::ConnectorError::RequestEncodingFailed)?
.as_str() .as_str()
@ -3878,13 +3931,13 @@ pub fn get_dispute_status(
"firstchargeback" | "retrievalrequest" | "rapiddisputeresolution" => { "firstchargeback" | "retrievalrequest" | "rapiddisputeresolution" => {
Ok(api_models::enums::DisputeStatus::DisputeOpened) Ok(api_models::enums::DisputeStatus::DisputeOpened)
} }
_ => Err(errors::ConnectorError::NotSupported { dispute_cycle => Err(errors::ConnectorError::UnexpectedResponseError(
message: format!("Dispute status {dispute_cycle}"), bytes::Bytes::from(format!("Dispute Stage: {dispute_cycle}")),
connector: "worldpayvantiv", )
}
.into()), .into()),
} }
} }
}
fn convert_string_to_primitive_date( fn convert_string_to_primitive_date(
item: Option<String>, item: Option<String>,
@ -3914,7 +3967,7 @@ impl TryFrom<ChargebackCase> for DisputeSyncResponse {
amount, amount,
currency: item.chargeback_currency_type, currency: item.chargeback_currency_type,
dispute_stage: get_dispute_stage(item.cycle.clone())?, dispute_stage: get_dispute_stage(item.cycle.clone())?,
dispute_status: get_dispute_status(item.cycle.clone())?, dispute_status: get_dispute_status(item.cycle.clone(), item.activity)?,
connector_status: item.cycle.clone(), connector_status: item.cycle.clone(),
connector_dispute_id: item.case_id.clone(), connector_dispute_id: item.case_id.clone(),
connector_reason: item.reason_code_description.clone(), connector_reason: item.reason_code_description.clone(),
@ -4148,3 +4201,40 @@ impl
}) })
} }
} }
fn get_last_non_auxiliary_activity_type(activities: Vec<Activity>) -> Option<String> {
let auxiliary_activities: std::collections::HashSet<&'static str> = [
"Add Note",
"Attach Document",
"Attempted Attach Document",
"Delete Document",
"Update Document",
"Move To Error Queue",
"Assign to Vantiv",
"Assign To Merchant",
"Merchant Auto Assign",
"Issuer Recalled",
"Network Decision",
"Request Declined",
"Sent Gift",
"Successful PayPal",
]
.iter()
.copied()
.collect();
let mut last_non_auxiliary_activity = None;
for activity in activities {
let auxiliary_activity = activity
.activity_type
.as_deref()
.map(|activity_type| auxiliary_activities.contains(activity_type))
.unwrap_or(false);
if !auxiliary_activity {
last_non_auxiliary_activity = activity.activity_type.clone()
}
}
last_non_auxiliary_activity
}

View File

@ -276,8 +276,10 @@ pub async fn accept_dispute(
core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?; core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
let dispute_id = dispute.dispute_id.clone(); let dispute_id = dispute.dispute_id.clone();
common_utils::fp_utils::when( common_utils::fp_utils::when(
!(dispute.dispute_stage == storage_enums::DisputeStage::Dispute !core_utils::should_proceed_with_accept_dispute(
&& dispute.dispute_status == storage_enums::DisputeStatus::DisputeOpened), dispute.dispute_stage,
dispute.dispute_status,
),
|| { || {
metrics::ACCEPT_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]); metrics::ACCEPT_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]);
Err(errors::ApiErrorResponse::DisputeStatusValidationFailed { Err(errors::ApiErrorResponse::DisputeStatusValidationFailed {

View File

@ -929,6 +929,9 @@ pub fn validate_dispute_status(
DisputeStatus::DisputeChallenged DisputeStatus::DisputeChallenged
| DisputeStatus::DisputeWon | DisputeStatus::DisputeWon
| DisputeStatus::DisputeLost | DisputeStatus::DisputeLost
| DisputeStatus::DisputeAccepted
| DisputeStatus::DisputeCancelled
| DisputeStatus::DisputeExpired
), ),
DisputeStatus::DisputeWon => matches!(dispute_status, DisputeStatus::DisputeWon), DisputeStatus::DisputeWon => matches!(dispute_status, DisputeStatus::DisputeWon),
DisputeStatus::DisputeLost => matches!(dispute_status, DisputeStatus::DisputeLost), DisputeStatus::DisputeLost => matches!(dispute_status, DisputeStatus::DisputeLost),
@ -2614,12 +2617,27 @@ pub fn should_proceed_with_submit_evidence(
dispute_stage: DisputeStage, dispute_stage: DisputeStage,
dispute_status: DisputeStatus, dispute_status: DisputeStatus,
) -> bool { ) -> bool {
matches!(dispute_stage, DisputeStage::DisputeReversal) matches!(
|| matches!( dispute_stage,
DisputeStage::PreDispute
| DisputeStage::Dispute
| DisputeStage::PreArbitration
| DisputeStage::Arbitration
) && matches!(
dispute_status, dispute_status,
DisputeStatus::DisputeExpired DisputeStatus::DisputeOpened | DisputeStatus::DisputeChallenged
| DisputeStatus::DisputeCancelled )
| DisputeStatus::DisputeWon }
| DisputeStatus::DisputeLost,
pub fn should_proceed_with_accept_dispute(
dispute_stage: DisputeStage,
dispute_status: DisputeStatus,
) -> bool {
matches!(
dispute_stage,
DisputeStage::PreDispute | DisputeStage::Dispute | DisputeStage::PreArbitration
) && matches!(
dispute_status,
DisputeStatus::DisputeChallenged | DisputeStatus::DisputeOpened
) )
} }