fix(connectors): [Nuvei] payments, refunds and chargeback webhooks (#9378)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
AkshayaFoiger
2025-09-19 16:40:31 +05:30
committed by GitHub
parent 86609c6620
commit 9654d18d74
2 changed files with 749 additions and 179 deletions

View File

@ -1,7 +1,10 @@
pub mod transformers;
use std::sync::LazyLock;
use api_models::{payments::PaymentIdType, webhooks::IncomingWebhookEvent};
use api_models::{
payments::PaymentIdType,
webhooks::{IncomingWebhookEvent, RefundIdType},
};
use common_enums::{enums, CallConnectorAction, PaymentAction};
use common_utils::{
crypto,
@ -9,7 +12,10 @@ use common_utils::{
ext_traits::{ByteSliceExt, BytesExt, ValueExt},
id_type,
request::{Method, Request, RequestBuilder, RequestContent},
types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector},
types::{
AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector, StringMajorUnit,
StringMajorUnitForConnector, StringMinorUnit, StringMinorUnitForConnector,
},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
@ -44,7 +50,7 @@ use hyperswitch_interfaces::{
ConnectorSpecifications, ConnectorValidation,
},
configs::Connectors,
errors,
disputes, errors,
events::connector_api_logs::ConnectorEvent,
types::{self, Response},
webhooks::{IncomingWebhook, IncomingWebhookRequestDetails},
@ -62,11 +68,17 @@ use crate::{
#[derive(Clone)]
pub struct Nuvei {
pub amount_convertor: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
amount_converter_string_minor_unit:
&'static (dyn AmountConvertor<Output = StringMinorUnit> + Sync),
amount_converter_float_major_unit:
&'static (dyn AmountConvertor<Output = FloatMajorUnit> + Sync),
}
impl Nuvei {
pub fn new() -> &'static Self {
&Self {
amount_convertor: &StringMajorUnitForConnector,
amount_converter_string_minor_unit: &StringMinorUnitForConnector,
amount_converter_float_major_unit: &FloatMajorUnitForConnector,
}
}
}
@ -546,13 +558,14 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Nuv
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
let response: NuveiTransactionSyncResponse = res
let nuvie_psync_common_response: nuvei::NuveiPaymentSyncResponse = res
.response
.parse_struct("NuveiTransactionSyncResponse")
.parse_struct("NuveiPaymentSyncResponse")
.switch()?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
event_builder.map(|i| i.set_response_body(&nuvie_psync_common_response));
router_env::logger::info!(connector_response=?nuvie_psync_common_response);
let response = NuveiTransactionSyncResponse::from(nuvie_psync_common_response);
RouterData::try_from(ResponseRouterData {
response,
@ -987,7 +1000,30 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Nuvei {
}
}
impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Nuvei {}
impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Nuvei {
fn handle_response(
&self,
data: &RefundsRouterData<RSync>,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<RouterData<RSync, RefundsData, RefundsResponseData>, errors::ConnectorError>
{
let nuvie_rsync_common_response: nuvei::PaymentDmnNotification = res
.response
.parse_struct("PaymentDmnNotification")
.switch()?;
event_builder.map(|i| i.set_response_body(&nuvie_rsync_common_response));
router_env::logger::info!(connector_response=?nuvie_rsync_common_response);
let response = NuveiTransactionSyncResponse::from(nuvie_rsync_common_response);
RouterData::try_from(ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
}
#[async_trait::async_trait]
impl IncomingWebhook for Nuvei {
@ -1003,8 +1039,19 @@ impl IncomingWebhook for Nuvei {
request: &IncomingWebhookRequestDetails<'_>,
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
let signature = utils::get_header_key_value("advanceResponseChecksum", request.headers)?;
hex::decode(signature).change_context(errors::ConnectorError::WebhookResponseEncodingFailed)
let webhook = get_webhook_object_from_body(request.body)?;
let nuvei_notification_signature = match webhook {
nuvei::NuveiWebhook::PaymentDmn(notification) => notification
.advance_response_checksum
.ok_or(errors::ConnectorError::WebhookSignatureNotFound)?,
nuvei::NuveiWebhook::Chargeback(_) => {
utils::get_header_key_value("Checksum", request.headers)?.to_string()
}
};
hex::decode(nuvei_notification_signature)
.change_context(errors::ConnectorError::WebhookSignatureNotFound)
}
fn get_webhook_source_verification_message(
@ -1014,9 +1061,7 @@ impl IncomingWebhook for Nuvei {
connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
// Parse the webhook payload
let webhook = serde_urlencoded::from_str::<nuvei::NuveiWebhook>(&request.query_params)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
let webhook = get_webhook_object_from_body(request.body)?;
let secret_str = std::str::from_utf8(&connector_webhook_secrets.secret)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
@ -1025,38 +1070,29 @@ impl IncomingWebhook for Nuvei {
nuvei::NuveiWebhook::PaymentDmn(notification) => {
// For payment DMNs, use the same format as before
let status = notification
.transaction_status
.status
.as_ref()
.map(|s| format!("{s:?}").to_uppercase())
.unwrap_or_else(|| "UNKNOWN".to_string());
.unwrap_or_default();
let to_sign = transformers::concat_strings(&[
secret_str.to_string(),
notification.total_amount.unwrap_or_default(),
notification.currency.unwrap_or_default(),
notification.response_time_stamp.unwrap_or_default(),
notification.ppp_transaction_id.unwrap_or_default(),
notification.total_amount,
notification.currency,
notification.response_time_stamp,
notification.ppp_transaction_id,
status,
notification.product_id.unwrap_or_default(),
notification.product_id.unwrap_or("NA".to_string()),
]);
Ok(to_sign.into_bytes())
}
nuvei::NuveiWebhook::Chargeback(notification) => {
// For chargeback notifications, use a different format based on Nuvei's documentation
// Note: This is a placeholder - you'll need to adjust based on Nuvei's actual chargeback signature format
let status = notification
.status
.as_ref()
.map(|s| format!("{s:?}").to_uppercase())
.unwrap_or_else(|| "UNKNOWN".to_string());
let response = serde_json::to_string(&notification)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
let to_sign = transformers::concat_strings(&[
secret_str.to_string(),
notification.chargeback_amount.unwrap_or_default(),
notification.chargeback_currency.unwrap_or_default(),
notification.ppp_transaction_id.unwrap_or_default(),
status,
]);
let to_sign = format!("{secret_str}{response}");
Ok(to_sign.into_bytes())
}
}
@ -1067,22 +1103,45 @@ impl IncomingWebhook for Nuvei {
request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
// Parse the webhook payload
let webhook = serde_urlencoded::from_str::<nuvei::NuveiWebhook>(&request.query_params)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
let webhook = get_webhook_object_from_body(request.body)?;
// Extract transaction ID from the webhook
let transaction_id = match &webhook {
nuvei::NuveiWebhook::PaymentDmn(notification) => {
notification.ppp_transaction_id.clone().unwrap_or_default()
}
match &webhook {
nuvei::NuveiWebhook::PaymentDmn(notification) => match notification.transaction_type {
Some(nuvei::NuveiTransactionType::Auth)
| Some(nuvei::NuveiTransactionType::Sale)
| Some(nuvei::NuveiTransactionType::Settle)
| Some(nuvei::NuveiTransactionType::Void)
| Some(nuvei::NuveiTransactionType::Auth3D)
| Some(nuvei::NuveiTransactionType::InitAuth3D) => {
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
PaymentIdType::ConnectorTransactionId(
notification
.transaction_id
.clone()
.ok_or(errors::ConnectorError::MissingConnectorTransactionID)?,
),
))
}
Some(nuvei::NuveiTransactionType::Credit) => {
Ok(api_models::webhooks::ObjectReferenceId::RefundId(
RefundIdType::ConnectorRefundId(
notification
.transaction_id
.clone()
.ok_or(errors::ConnectorError::MissingConnectorRefundID)?,
),
))
}
None => Err(errors::ConnectorError::WebhookEventTypeNotFound.into()),
},
nuvei::NuveiWebhook::Chargeback(notification) => {
notification.ppp_transaction_id.clone().unwrap_or_default()
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
PaymentIdType::ConnectorTransactionId(
notification.transaction_details.transaction_id.to_string(),
),
))
}
};
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
PaymentIdType::ConnectorTransactionId(transaction_id),
))
}
}
fn get_webhook_event_type(
@ -1090,27 +1149,25 @@ impl IncomingWebhook for Nuvei {
request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<IncomingWebhookEvent, errors::ConnectorError> {
// Parse the webhook payload
let webhook = serde_urlencoded::from_str::<nuvei::NuveiWebhook>(&request.query_params)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
let webhook = get_webhook_object_from_body(request.body)?;
// Map webhook type to event type
match webhook {
nuvei::NuveiWebhook::PaymentDmn(notification) => {
match notification.transaction_status {
Some(nuvei::TransactionStatus::Approved)
| Some(nuvei::TransactionStatus::Settled) => {
Ok(IncomingWebhookEvent::PaymentIntentSuccess)
}
Some(nuvei::TransactionStatus::Declined)
| Some(nuvei::TransactionStatus::Error) => {
Ok(IncomingWebhookEvent::PaymentIntentFailure)
}
_ => Ok(IncomingWebhookEvent::EventNotSupported),
if let Some((status, transaction_type)) =
notification.status.zip(notification.transaction_type)
{
nuvei::map_notification_to_event(status, transaction_type)
} else {
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
}
}
nuvei::NuveiWebhook::Chargeback(_) => {
// Chargeback notifications always map to dispute opened
Ok(IncomingWebhookEvent::DisputeOpened)
nuvei::NuveiWebhook::Chargeback(notification) => {
if let Some(dispute_event) = notification.chargeback.dispute_unified_status_code {
nuvei::map_dispute_notification_to_event(dispute_event)
} else {
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
}
}
}
}
@ -1119,12 +1176,72 @@ impl IncomingWebhook for Nuvei {
&self,
request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
// Parse the webhook payload
let webhook = serde_urlencoded::from_str::<nuvei::NuveiWebhook>(&request.query_params)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
// Convert webhook to payments response
let payment_response = NuveiPaymentsResponse::from(webhook);
Ok(Box::new(payment_response))
let notification = get_webhook_object_from_body(request.body)?;
Ok(Box::new(notification))
}
fn get_dispute_details(
&self,
request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<disputes::DisputePayload, errors::ConnectorError> {
let webhook = request
.body
.parse_struct::<nuvei::ChargebackNotification>("ChargebackNotification")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let currency = webhook
.chargeback
.reported_currency
.to_uppercase()
.parse::<enums::Currency>()
.map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?;
let amount_minorunit = utils::convert_back_amount_to_minor_units(
self.amount_converter_float_major_unit,
webhook.chargeback.reported_amount,
currency,
)?;
let amount = utils::convert_amount(
self.amount_converter_string_minor_unit,
amount_minorunit,
currency,
)?;
let dispute_unified_status_code = webhook
.chargeback
.dispute_unified_status_code
.ok_or(errors::ConnectorError::WebhookEventTypeNotFound)?;
let connector_dispute_id = webhook
.chargeback
.dispute_id
.ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?;
Ok(disputes::DisputePayload {
amount,
currency,
dispute_stage: api_models::enums::DisputeStage::from(
dispute_unified_status_code.clone(),
),
connector_dispute_id,
connector_reason: webhook.chargeback.chargeback_reason,
connector_reason_code: webhook.chargeback.chargeback_reason_category,
challenge_required_by: webhook.chargeback.dispute_due_date,
connector_status: dispute_unified_status_code.to_string(),
created_at: webhook.chargeback.date,
updated_at: None,
})
}
}
fn get_webhook_object_from_body(
body: &[u8],
) -> CustomResult<nuvei::NuveiWebhook, errors::ConnectorError> {
let payments_response = serde_urlencoded::from_bytes::<nuvei::NuveiWebhook>(body)
.change_context(errors::ConnectorError::ResponseDeserializationFailed);
match payments_response {
Ok(webhook) => Ok(webhook),
Err(_) => body
.parse_struct::<nuvei::NuveiWebhook>("NuveiWebhook")
.change_context(errors::ConnectorError::ResponseDeserializationFailed),
}
}
@ -1325,7 +1442,8 @@ static NUVEI_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
integration_status: enums::ConnectorIntegrationStatus::Beta,
};
static NUVEI_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 1] = [enums::EventClass::Payments];
static NUVEI_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 2] =
[enums::EventClass::Payments, enums::EventClass::Disputes];
impl ConnectorSpecifications for Nuvei {
fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {

View File

@ -8,7 +8,7 @@ use common_utils::{
id_type::CustomerId,
pii::{self, Email, IpAddress},
request::Method,
types::{MinorUnit, StringMajorUnit, StringMajorUnitForConnector},
types::{FloatMajorUnit, MinorUnit, StringMajorUnit, StringMajorUnitForConnector},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
@ -925,6 +925,19 @@ pub fn encode_payload(
Ok(hex::encode(digest))
}
impl From<NuveiPaymentSyncResponse> for NuveiTransactionSyncResponse {
fn from(value: NuveiPaymentSyncResponse) -> Self {
match value {
NuveiPaymentSyncResponse::NuveiDmn(payment_dmn_notification) => {
Self::from(*payment_dmn_notification)
}
NuveiPaymentSyncResponse::NuveiApi(nuvei_transaction_sync_response) => {
*nuvei_transaction_sync_response
}
}
}
}
impl TryFrom<&types::PaymentsAuthorizeSessionTokenRouterData> for NuveiSessionRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
@ -1503,7 +1516,7 @@ where
card_holder_name: data.card_holder_name,
expiration_month: Some(data.card_exp_month),
expiration_year: Some(data.card_exp_year),
..Default::default() // CVV should be disabled by nuvei fo
..Default::default() // CVV should be disabled by nuvei
}),
redirect_url: None,
user_payment_option_id: None,
@ -2340,6 +2353,7 @@ pub struct NuveiTransactionSyncResponseDetails {
gw_error_reason: Option<String>,
gw_extended_error_code: Option<i64>,
transaction_id: Option<String>,
// Status of the payment
transaction_status: Option<NuveiTransactionStatus>,
transaction_type: Option<NuveiTransactionType>,
auth_code: Option<String>,
@ -2357,6 +2371,7 @@ pub struct NuveiTransactionSyncResponse {
pub fraud_details: Option<FraudDetails>,
pub client_unique_id: Option<String>,
pub internal_request_id: Option<i64>,
// API response status
pub status: NuveiPaymentStatus,
pub err_code: Option<i64>,
pub reason: Option<String>,
@ -2519,6 +2534,7 @@ struct ErrorResponseParams {
gw_error_code: Option<i64>,
gw_error_reason: Option<String>,
transaction_status: Option<NuveiTransactionStatus>,
transaction_id: Option<String>,
}
fn build_error_response(params: ErrorResponseParams) -> Option<ErrorResponse> {
@ -2530,6 +2546,7 @@ fn build_error_response(params: ErrorResponseParams) -> Option<ErrorResponse> {
params.merchant_advice_code.clone(),
params.gw_error_code.map(|code| code.to_string()),
params.gw_error_reason.clone(),
params.transaction_id.clone(),
)),
_ => {
@ -2540,6 +2557,7 @@ fn build_error_response(params: ErrorResponseParams) -> Option<ErrorResponse> {
params.merchant_advice_code,
params.gw_error_code.map(|e| e.to_string()),
params.gw_error_reason.clone(),
params.transaction_id.clone(),
));
match params.transaction_status {
@ -2630,6 +2648,7 @@ impl
gw_error_code: response.gw_error_code,
gw_error_reason: response.gw_error_reason.clone(),
transaction_status: response.transaction_status.clone(),
transaction_id: response.transaction_id.clone(),
}) {
Err(err)
} else {
@ -2852,6 +2871,7 @@ impl
gw_error_code: response.gw_error_code,
gw_error_reason: response.gw_error_reason.clone(),
transaction_status: response.transaction_status.clone(),
transaction_id: response.transaction_id.clone(),
}) {
Err(err)
} else {
@ -2914,6 +2934,7 @@ where
gw_error_code: response.gw_error_code,
gw_error_reason: response.gw_error_reason.clone(),
transaction_status: response.transaction_status.clone(),
transaction_id: response.transaction_id.clone(),
}) {
Err(err)
} else {
@ -2984,6 +3005,9 @@ where
transaction_status: transaction_details
.as_ref()
.and_then(|details| details.transaction_status.clone()),
transaction_id: transaction_details
.as_ref()
.and_then(|details| details.transaction_id.clone()),
}) {
Err(err)
} else {
@ -3075,21 +3099,70 @@ impl TryFrom<RefundsResponseRouterData<Execute, NuveiPaymentsResponse>>
}
}
impl TryFrom<RefundsResponseRouterData<RSync, NuveiPaymentsResponse>>
impl TryFrom<RefundsResponseRouterData<RSync, NuveiTransactionSyncResponse>>
for types::RefundsRouterData<RSync>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: RefundsResponseRouterData<RSync, NuveiPaymentsResponse>,
item: RefundsResponseRouterData<RSync, NuveiTransactionSyncResponse>,
) -> Result<Self, Self::Error> {
let transaction_id = item
let txn_id = item
.response
.transaction_id
.clone()
.transaction_details
.as_ref()
.and_then(|details| details.transaction_id.clone())
.ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;
let refund_response =
get_refund_response(item.response.clone(), item.http_code, transaction_id);
let refund_status = item
.response
.transaction_details
.as_ref()
.and_then(|details| details.transaction_status.clone())
.map(enums::RefundStatus::from)
.unwrap_or(enums::RefundStatus::Failure);
let network_decline_code = item
.response
.transaction_details
.as_ref()
.and_then(|details| details.gw_error_code.map(|e| e.to_string()));
let network_error_msg = item
.response
.transaction_details
.as_ref()
.and_then(|details| details.gw_error_reason.clone());
let refund_response = match item.response.status {
NuveiPaymentStatus::Error => Err(Box::new(get_error_response(
item.response.err_code,
item.response.reason.clone(),
item.http_code,
item.response.merchant_advice_code,
network_decline_code,
network_error_msg,
Some(txn_id.clone()),
))),
_ => match item
.response
.transaction_details
.and_then(|nuvei_response| nuvei_response.transaction_status)
{
Some(NuveiTransactionStatus::Error) => Err(Box::new(get_error_response(
item.response.err_code,
item.response.reason,
item.http_code,
item.response.merchant_advice_code,
network_decline_code,
network_error_msg,
Some(txn_id.clone()),
))),
_ => Ok(RefundsResponseData {
connector_refund_id: txn_id,
refund_status,
}),
},
};
Ok(Self {
response: refund_response.map_err(|err| *err),
@ -3175,6 +3248,7 @@ fn get_refund_response(
response.merchant_advice_code,
response.gw_error_code.map(|e| e.to_string()),
response.gw_error_reason,
Some(txn_id.clone()),
))),
_ => match response.transaction_status {
Some(NuveiTransactionStatus::Error) => Err(Box::new(get_error_response(
@ -3184,6 +3258,7 @@ fn get_refund_response(
response.merchant_advice_code,
response.gw_error_code.map(|e| e.to_string()),
response.gw_error_reason,
Some(txn_id.clone()),
))),
_ => Ok(RefundsResponseData {
connector_refund_id: txn_id,
@ -3200,6 +3275,7 @@ fn get_error_response(
network_advice_code: Option<String>,
network_decline_code: Option<String>,
network_error_message: Option<String>,
transaction_id: Option<String>,
) -> ErrorResponse {
ErrorResponse {
code: error_code
@ -3211,7 +3287,7 @@ fn get_error_response(
reason: None,
status_code: http_code,
attempt_status: None,
connector_transaction_id: None,
connector_transaction_id: transaction_id,
network_advice_code: network_advice_code.clone(),
network_decline_code: network_decline_code.clone(),
network_error_message: network_error_message.clone(),
@ -3227,6 +3303,14 @@ pub enum NuveiWebhook {
Chargeback(ChargebackNotification),
}
/// Represents Psync Response from Nuvei.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum NuveiPaymentSyncResponse {
NuveiDmn(Box<PaymentDmnNotification>),
NuveiApi(Box<NuveiTransactionSyncResponse>),
}
/// Represents the status of a chargeback event.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ChargebackStatus {
@ -3243,27 +3327,225 @@ pub enum ChargebackStatus {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChargebackNotification {
#[serde(rename = "ppp_TransactionID")]
pub ppp_transaction_id: Option<String>,
pub merchant_unique_id: Option<String>,
pub merchant_id: Option<String>,
pub merchant_site_id: Option<String>,
pub request_version: Option<String>,
pub message: Option<String>,
pub status: Option<ChargebackStatus>,
pub reason: Option<String>,
pub case_id: Option<String>,
pub processor_case_id: Option<String>,
pub client_id: Option<i64>,
pub client_name: Option<String>,
pub event_date_u_t_c: Option<String>,
pub event_correlation_id: Option<String>,
pub chargeback: ChargebackData,
pub transaction_details: ChargebackTransactionDetails,
pub event_id: Option<String>,
pub event_date: Option<String>,
pub processing_entity_type: Option<String>,
pub processing_entity_id: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ChargebackData {
pub date: Option<time::PrimitiveDateTime>,
pub chargeback_status_category: ChargebackStatusCategory,
#[serde(rename = "Type")]
pub webhook_type: ChargebackType,
pub status: Option<String>,
pub amount: FloatMajorUnit,
pub currency: String,
pub reported_amount: FloatMajorUnit,
pub reported_currency: String,
pub chargeback_reason: Option<String>,
pub chargeback_reason_category: Option<String>,
pub reason_message: Option<String>,
pub dispute_id: Option<String>,
pub dispute_due_date: Option<time::PrimitiveDateTime>,
pub dispute_event_id: Option<i64>,
pub dispute_unified_status_code: Option<DisputeUnifiedStatusCode>,
}
#[derive(Debug, Clone, Serialize, Deserialize, strum::Display)]
pub enum DisputeUnifiedStatusCode {
#[serde(rename = "FC")]
FirstChargebackInitiatedByIssuer,
#[serde(rename = "CC")]
CreditChargebackInitiatedByIssuer,
#[serde(rename = "CC-A-ACPT")]
CreditChargebackAcceptedAutomatically,
#[serde(rename = "FC-A-EPRD")]
FirstChargebackNoResponseExpired,
#[serde(rename = "FC-M-ACPT")]
FirstChargebackAcceptedByMerchant,
#[serde(rename = "FC-A-ACPT")]
FirstChargebackAcceptedAutomatically,
#[serde(rename = "FC-A-ACPT-MCOLL")]
FirstChargebackAcceptedAutomaticallyMcoll,
#[serde(rename = "FC-M-PART")]
FirstChargebackPartiallyAcceptedByMerchant,
#[serde(rename = "FC-M-PART-EXP")]
FirstChargebackPartiallyAcceptedByMerchantExpired,
#[serde(rename = "FC-M-RJCT")]
FirstChargebackRejectedByMerchant,
#[serde(rename = "FC-M-RJCT-EXP")]
FirstChargebackRejectedByMerchantExpired,
#[serde(rename = "FC-A-RJCT")]
FirstChargebackRejectedAutomatically,
#[serde(rename = "FC-A-RJCT-EXP")]
FirstChargebackRejectedAutomaticallyExpired,
#[serde(rename = "IPA")]
PreArbitrationInitiatedByIssuer,
#[serde(rename = "MPA-I-ACPT")]
MerchantPreArbitrationAcceptedByIssuer,
#[serde(rename = "MPA-I-RJCT")]
MerchantPreArbitrationRejectedByIssuer,
#[serde(rename = "MPA-I-PART")]
MerchantPreArbitrationPartiallyAcceptedByIssuer,
#[serde(rename = "FC-CLSD-MF")]
FirstChargebackClosedMerchantFavour,
#[serde(rename = "FC-CLSD-CHF")]
FirstChargebackClosedCardholderFavour,
#[serde(rename = "FC-CLSD-RCL")]
FirstChargebackClosedRecall,
#[serde(rename = "FC-I-RCL")]
FirstChargebackRecalledByIssuer,
#[serde(rename = "PA-CLSD-MF")]
PreArbitrationClosedMerchantFavour,
#[serde(rename = "PA-CLSD-CHF")]
PreArbitrationClosedCardholderFavour,
#[serde(rename = "RDR")]
Rdr,
#[serde(rename = "FC-SPCSE")]
FirstChargebackDisputeResponseNotAllowed,
#[serde(rename = "MCC")]
McCollaborationInitiatedByIssuer,
#[serde(rename = "MCC-A-RJCT")]
McCollaborationPreviouslyRefundedAuto,
#[serde(rename = "MCC-M-ACPT")]
McCollaborationRefundedByMerchant,
#[serde(rename = "MCC-EXPR")]
McCollaborationExpired,
#[serde(rename = "MCC-M-RJCT")]
McCollaborationRejectedByMerchant,
#[serde(rename = "MCC-A-ACPT")]
McCollaborationAutomaticAccept,
#[serde(rename = "MCC-CLSD-MF")]
McCollaborationClosedMerchantFavour,
#[serde(rename = "MCC-CLSD-CHF")]
McCollaborationClosedCardholderFavour,
#[serde(rename = "INQ")]
InquiryInitiatedByIssuer,
#[serde(rename = "INQ-M-RSP")]
InquiryRespondedByMerchant,
#[serde(rename = "INQ-EXPR")]
InquiryExpired,
#[serde(rename = "INQ-A-RJCT")]
InquiryAutomaticallyRejected,
#[serde(rename = "INQ-A-CNLD")]
InquiryCancelledAfterRefund,
#[serde(rename = "INQ-M-RFND")]
InquiryAcceptedFullRefund,
#[serde(rename = "INQ-M-P-RFND")]
InquiryPartialAcceptedPartialRefund,
#[serde(rename = "INQ-UPD")]
InquiryUpdated,
#[serde(rename = "IPA-M-ACPT")]
PreArbitrationAcceptedByMerchant,
#[serde(rename = "IPA-M-PART")]
PreArbitrationPartiallyAcceptedByMerchant,
#[serde(rename = "IPA-M-PART-EXP")]
PreArbitrationPartiallyAcceptedByMerchantExpired,
#[serde(rename = "IPA-M-RJCT")]
PreArbitrationRejectedByMerchant,
#[serde(rename = "IPA-M-RJCT-EXP")]
PreArbitrationRejectedByMerchantExpired,
#[serde(rename = "IPA-A-ACPT")]
PreArbitrationAutomaticallyAcceptedByMerchant,
#[serde(rename = "PA-CLSD-RC")]
PreArbitrationClosedRecall,
#[serde(rename = "IPAR-M-ACPT")]
RejectedPreArbAcceptedByMerchant,
#[serde(rename = "IPAR-A-ACPT")]
RejectedPreArbExpiredAutoAccepted,
#[serde(rename = "CC-I-RCLL")]
CreditChargebackRecalledByIssuer,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ChargebackTransactionDetails {
pub transaction_id: i64,
pub transaction_date: Option<String>,
pub client_unique_id: Option<String>,
pub acquirer_name: Option<String>,
pub masked_card_number: Option<String>,
pub arn: Option<String>,
pub retrieval_request_date: Option<String>,
pub chargeback_date: Option<String>,
pub chargeback_amount: Option<String>,
pub chargeback_currency: Option<String>,
pub original_amount: Option<String>,
pub original_currency: Option<String>,
#[serde(rename = "transactionID")]
pub transaction_id: Option<String>,
pub user_token_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum ChargebackType {
Chargeback,
Retrieval,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ChargebackStatusCategory {
#[serde(rename = "Regular")]
Regular,
#[serde(rename = "cancelled")]
Cancelled,
#[serde(rename = "Duplicate")]
Duplicate,
#[serde(rename = "RDR-Refund")]
RdrRefund,
#[serde(rename = "Soft_CB")]
SoftCb,
}
/// Represents the overall status of the DMN.
@ -3271,8 +3553,19 @@ pub struct ChargebackNotification {
#[serde(rename_all = "UPPERCASE")]
pub enum DmnStatus {
Success,
Approved,
Error,
Pending,
Declined,
}
/// Represents the transaction status of the DMN
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum DmnApiTransactionStatus {
Ok,
Fail,
Pending,
}
/// Represents the status of the transaction itself.
@ -3288,63 +3581,51 @@ pub enum TransactionStatus {
Settled,
}
/// Represents the type of transaction.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub enum PaymentTransactionType {
Auth,
Sale,
Settle,
Credit,
Void,
Auth3D,
Sale3D,
Verif,
}
/// Represents a Payment Direct Merchant Notification (DMN) webhook.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentDmnNotification {
// Status of the Api transaction
#[serde(rename = "ppp_status")]
pub ppp_status: DmnApiTransactionStatus,
#[serde(rename = "PPP_TransactionID")]
pub ppp_transaction_id: Option<String>,
pub ppp_transaction_id: String,
pub total_amount: String,
pub currency: String,
#[serde(rename = "TransactionID")]
pub transaction_id: Option<String>,
// Status of the Payment
#[serde(rename = "Status")]
pub status: Option<DmnStatus>,
pub transaction_type: Option<NuveiTransactionType>,
#[serde(rename = "ErrCode")]
pub err_code: Option<String>,
#[serde(rename = "ExErrCode")]
pub ex_err_code: Option<String>,
pub desc: Option<String>,
pub merchant_unique_id: Option<String>,
pub custom_data: Option<String>,
pub product_id: Option<String>,
pub first_name: Option<String>,
pub last_name: Option<String>,
pub email: Option<String>,
pub total_amount: Option<String>,
pub currency: Option<String>,
pub fee: Option<String>,
#[serde(rename = "AuthCode")]
pub auth_code: Option<String>,
pub transaction_status: Option<TransactionStatus>,
pub transaction_type: Option<PaymentTransactionType>,
#[serde(rename = "Reason")]
pub reason: Option<String>,
#[serde(rename = "ReasonCode")]
pub reason_code: Option<String>,
#[serde(rename = "user_token_id")]
pub user_token_id: Option<String>,
#[serde(rename = "payment_method")]
pub payment_method: Option<String>,
#[serde(rename = "responseTimeStamp")]
pub response_time_stamp: Option<String>,
pub response_time_stamp: String,
#[serde(rename = "invoice_id")]
pub invoice_id: Option<String>,
#[serde(rename = "merchant_id")]
pub merchant_id: Option<String>,
pub merchant_id: Option<Secret<String>>,
#[serde(rename = "merchant_site_id")]
pub merchant_site_id: Option<String>,
pub merchant_site_id: Option<Secret<String>>,
#[serde(rename = "responsechecksum")]
pub response_checksum: Option<String>,
#[serde(rename = "advanceResponseChecksum")]
pub advance_response_checksum: Option<String>,
pub product_id: Option<String>,
pub merchant_advice_code: Option<String>,
#[serde(rename = "AuthCode")]
pub auth_code: Option<String>,
pub acquirer_bank: Option<String>,
pub client_request_id: Option<String>,
}
// For backward compatibility with existing code
@ -3354,56 +3635,42 @@ pub struct NuveiWebhookTransactionId {
pub ppp_transaction_id: String,
}
// Helper struct to extract transaction ID from either webhook type
impl From<&NuveiWebhook> for NuveiWebhookTransactionId {
fn from(webhook: &NuveiWebhook) -> Self {
match webhook {
NuveiWebhook::Chargeback(notification) => Self {
ppp_transaction_id: notification.ppp_transaction_id.clone().unwrap_or_default(),
},
NuveiWebhook::PaymentDmn(notification) => Self {
ppp_transaction_id: notification.ppp_transaction_id.clone().unwrap_or_default(),
},
}
}
}
// Convert webhook to payments response for further processing
impl From<NuveiWebhook> for NuveiPaymentsResponse {
fn from(webhook: NuveiWebhook) -> Self {
match webhook {
NuveiWebhook::Chargeback(notification) => Self {
transaction_status: Some(NuveiTransactionStatus::Processing),
transaction_id: notification.transaction_id,
transaction_type: Some(NuveiTransactionType::Credit), // Using Credit as placeholder for chargeback
..Default::default()
impl From<PaymentDmnNotification> for NuveiTransactionSyncResponse {
fn from(notification: PaymentDmnNotification) -> Self {
Self {
status: match notification.ppp_status {
DmnApiTransactionStatus::Ok => NuveiPaymentStatus::Success,
DmnApiTransactionStatus::Fail => NuveiPaymentStatus::Failed,
DmnApiTransactionStatus::Pending => NuveiPaymentStatus::Processing,
},
NuveiWebhook::PaymentDmn(notification) => {
let transaction_type = notification.transaction_type.map(|tt| match tt {
PaymentTransactionType::Auth => NuveiTransactionType::Auth,
PaymentTransactionType::Sale => NuveiTransactionType::Sale,
PaymentTransactionType::Settle => NuveiTransactionType::Settle,
PaymentTransactionType::Credit => NuveiTransactionType::Credit,
PaymentTransactionType::Void => NuveiTransactionType::Void,
PaymentTransactionType::Auth3D => NuveiTransactionType::Auth3D,
PaymentTransactionType::Sale3D => NuveiTransactionType::Auth3D, // Map to closest equivalent
PaymentTransactionType::Verif => NuveiTransactionType::Auth, // Map to closest equivalent
});
Self {
transaction_status: notification.transaction_status.map(|ts| match ts {
TransactionStatus::Approved => NuveiTransactionStatus::Approved,
TransactionStatus::Declined => NuveiTransactionStatus::Declined,
TransactionStatus::Error => NuveiTransactionStatus::Error,
TransactionStatus::Settled => NuveiTransactionStatus::Approved,
_ => NuveiTransactionStatus::Processing,
}),
transaction_id: notification.transaction_id,
transaction_type,
..Default::default()
}
}
err_code: notification
.err_code
.and_then(|code| code.parse::<i64>().ok()),
reason: notification.reason.clone(),
transaction_details: Some(NuveiTransactionSyncResponseDetails {
gw_error_code: notification
.reason_code
.and_then(|code| code.parse::<i64>().ok()),
gw_error_reason: notification.reason.clone(),
gw_extended_error_code: None,
transaction_id: notification.transaction_id,
transaction_status: notification.status.map(|ts| match ts {
DmnStatus::Success | DmnStatus::Approved => NuveiTransactionStatus::Approved,
DmnStatus::Declined => NuveiTransactionStatus::Declined,
DmnStatus::Pending => NuveiTransactionStatus::Pending,
DmnStatus::Error => NuveiTransactionStatus::Error,
}),
transaction_type: notification.transaction_type,
auth_code: notification.auth_code,
processed_amount: None,
processed_currency: None,
acquiring_bank_name: notification.acquirer_bank,
}),
merchant_id: notification.merchant_id,
merchant_site_id: notification.merchant_site_id,
merchant_advice_code: notification.merchant_advice_code,
..Default::default()
}
}
}
@ -3479,3 +3746,188 @@ fn convert_to_additional_payment_method_connector_response(
Err(_) => None,
}
}
pub fn map_notification_to_event(
status: DmnStatus,
transaction_type: NuveiTransactionType,
) -> Result<api_models::webhooks::IncomingWebhookEvent, error_stack::Report<errors::ConnectorError>>
{
match (status, transaction_type) {
(DmnStatus::Success | DmnStatus::Approved, NuveiTransactionType::Auth) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess)
}
(DmnStatus::Success | DmnStatus::Approved, NuveiTransactionType::Sale) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess)
}
(DmnStatus::Success | DmnStatus::Approved, NuveiTransactionType::Settle) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentCaptureSuccess)
}
(DmnStatus::Success | DmnStatus::Approved, NuveiTransactionType::Void) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentCancelled)
}
(DmnStatus::Success | DmnStatus::Approved, NuveiTransactionType::Credit) => {
Ok(api_models::webhooks::IncomingWebhookEvent::RefundSuccess)
}
(DmnStatus::Error | DmnStatus::Declined, NuveiTransactionType::Auth) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationFailure)
}
(DmnStatus::Error | DmnStatus::Declined, NuveiTransactionType::Sale) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure)
}
(DmnStatus::Error | DmnStatus::Declined, NuveiTransactionType::Settle) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentCaptureFailure)
}
(DmnStatus::Error | DmnStatus::Declined, NuveiTransactionType::Void) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentCancelFailure)
}
(DmnStatus::Error | DmnStatus::Declined, NuveiTransactionType::Credit) => {
Ok(api_models::webhooks::IncomingWebhookEvent::RefundFailure)
}
(
DmnStatus::Pending,
NuveiTransactionType::Auth | NuveiTransactionType::Sale | NuveiTransactionType::Settle,
) => Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentProcessing),
_ => Err(errors::ConnectorError::WebhookEventTypeNotFound.into()),
}
}
pub fn map_dispute_notification_to_event(
dispute_code: DisputeUnifiedStatusCode,
) -> Result<api_models::webhooks::IncomingWebhookEvent, error_stack::Report<errors::ConnectorError>>
{
match dispute_code {
DisputeUnifiedStatusCode::FirstChargebackInitiatedByIssuer
| DisputeUnifiedStatusCode::CreditChargebackInitiatedByIssuer
| DisputeUnifiedStatusCode::McCollaborationInitiatedByIssuer
| DisputeUnifiedStatusCode::FirstChargebackClosedRecall
| DisputeUnifiedStatusCode::InquiryInitiatedByIssuer => {
Ok(api_models::webhooks::IncomingWebhookEvent::DisputeOpened)
}
DisputeUnifiedStatusCode::CreditChargebackAcceptedAutomatically
| DisputeUnifiedStatusCode::FirstChargebackAcceptedAutomatically
| DisputeUnifiedStatusCode::FirstChargebackAcceptedAutomaticallyMcoll
| DisputeUnifiedStatusCode::FirstChargebackAcceptedByMerchant
| DisputeUnifiedStatusCode::FirstChargebackDisputeResponseNotAllowed
| DisputeUnifiedStatusCode::Rdr
| DisputeUnifiedStatusCode::McCollaborationRefundedByMerchant
| DisputeUnifiedStatusCode::McCollaborationAutomaticAccept
| DisputeUnifiedStatusCode::InquiryAcceptedFullRefund
| DisputeUnifiedStatusCode::PreArbitrationAcceptedByMerchant
| DisputeUnifiedStatusCode::PreArbitrationPartiallyAcceptedByMerchant
| DisputeUnifiedStatusCode::PreArbitrationAutomaticallyAcceptedByMerchant
| DisputeUnifiedStatusCode::RejectedPreArbAcceptedByMerchant
| DisputeUnifiedStatusCode::RejectedPreArbExpiredAutoAccepted => {
Ok(api_models::webhooks::IncomingWebhookEvent::DisputeAccepted)
}
DisputeUnifiedStatusCode::FirstChargebackNoResponseExpired
| DisputeUnifiedStatusCode::FirstChargebackPartiallyAcceptedByMerchant
| DisputeUnifiedStatusCode::FirstChargebackClosedCardholderFavour
| DisputeUnifiedStatusCode::PreArbitrationClosedCardholderFavour
| DisputeUnifiedStatusCode::McCollaborationClosedCardholderFavour => {
Ok(api_models::webhooks::IncomingWebhookEvent::DisputeLost)
}
DisputeUnifiedStatusCode::FirstChargebackRejectedByMerchant
| DisputeUnifiedStatusCode::FirstChargebackRejectedAutomatically
| DisputeUnifiedStatusCode::PreArbitrationInitiatedByIssuer
| DisputeUnifiedStatusCode::MerchantPreArbitrationRejectedByIssuer
| DisputeUnifiedStatusCode::InquiryRespondedByMerchant
| DisputeUnifiedStatusCode::PreArbitrationRejectedByMerchant => {
Ok(api_models::webhooks::IncomingWebhookEvent::DisputeChallenged)
}
DisputeUnifiedStatusCode::FirstChargebackRejectedAutomaticallyExpired
| DisputeUnifiedStatusCode::FirstChargebackPartiallyAcceptedByMerchantExpired
| DisputeUnifiedStatusCode::FirstChargebackRejectedByMerchantExpired
| DisputeUnifiedStatusCode::McCollaborationExpired
| DisputeUnifiedStatusCode::InquiryExpired
| DisputeUnifiedStatusCode::PreArbitrationPartiallyAcceptedByMerchantExpired
| DisputeUnifiedStatusCode::PreArbitrationRejectedByMerchantExpired => {
Ok(api_models::webhooks::IncomingWebhookEvent::DisputeExpired)
}
DisputeUnifiedStatusCode::MerchantPreArbitrationAcceptedByIssuer
| DisputeUnifiedStatusCode::MerchantPreArbitrationPartiallyAcceptedByIssuer
| DisputeUnifiedStatusCode::FirstChargebackClosedMerchantFavour
| DisputeUnifiedStatusCode::McCollaborationClosedMerchantFavour
| DisputeUnifiedStatusCode::PreArbitrationClosedMerchantFavour => {
Ok(api_models::webhooks::IncomingWebhookEvent::DisputeWon)
}
DisputeUnifiedStatusCode::FirstChargebackRecalledByIssuer
| DisputeUnifiedStatusCode::InquiryCancelledAfterRefund
| DisputeUnifiedStatusCode::PreArbitrationClosedRecall
| DisputeUnifiedStatusCode::CreditChargebackRecalledByIssuer => {
Ok(api_models::webhooks::IncomingWebhookEvent::DisputeCancelled)
}
DisputeUnifiedStatusCode::McCollaborationPreviouslyRefundedAuto
| DisputeUnifiedStatusCode::McCollaborationRejectedByMerchant
| DisputeUnifiedStatusCode::InquiryAutomaticallyRejected
| DisputeUnifiedStatusCode::InquiryPartialAcceptedPartialRefund
| DisputeUnifiedStatusCode::InquiryUpdated => {
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
}
}
}
impl From<DisputeUnifiedStatusCode> for common_enums::DisputeStage {
fn from(code: DisputeUnifiedStatusCode) -> Self {
match code {
// --- PreDispute ---
DisputeUnifiedStatusCode::Rdr
| DisputeUnifiedStatusCode::InquiryInitiatedByIssuer
| DisputeUnifiedStatusCode::InquiryRespondedByMerchant
| DisputeUnifiedStatusCode::InquiryExpired
| DisputeUnifiedStatusCode::InquiryAutomaticallyRejected
| DisputeUnifiedStatusCode::InquiryCancelledAfterRefund
| DisputeUnifiedStatusCode::InquiryAcceptedFullRefund
| DisputeUnifiedStatusCode::InquiryPartialAcceptedPartialRefund
| DisputeUnifiedStatusCode::InquiryUpdated => Self::PreDispute,
// --- Dispute ---
DisputeUnifiedStatusCode::FirstChargebackInitiatedByIssuer
| DisputeUnifiedStatusCode::CreditChargebackInitiatedByIssuer
| DisputeUnifiedStatusCode::FirstChargebackNoResponseExpired
| DisputeUnifiedStatusCode::FirstChargebackAcceptedByMerchant
| DisputeUnifiedStatusCode::FirstChargebackAcceptedAutomatically
| DisputeUnifiedStatusCode::FirstChargebackAcceptedAutomaticallyMcoll
| DisputeUnifiedStatusCode::FirstChargebackPartiallyAcceptedByMerchant
| DisputeUnifiedStatusCode::FirstChargebackPartiallyAcceptedByMerchantExpired
| DisputeUnifiedStatusCode::FirstChargebackRejectedByMerchant
| DisputeUnifiedStatusCode::FirstChargebackRejectedByMerchantExpired
| DisputeUnifiedStatusCode::FirstChargebackRejectedAutomatically
| DisputeUnifiedStatusCode::FirstChargebackRejectedAutomaticallyExpired
| DisputeUnifiedStatusCode::FirstChargebackClosedMerchantFavour
| DisputeUnifiedStatusCode::FirstChargebackClosedCardholderFavour
| DisputeUnifiedStatusCode::FirstChargebackClosedRecall
| DisputeUnifiedStatusCode::FirstChargebackRecalledByIssuer
| DisputeUnifiedStatusCode::FirstChargebackDisputeResponseNotAllowed
| DisputeUnifiedStatusCode::McCollaborationInitiatedByIssuer
| DisputeUnifiedStatusCode::McCollaborationPreviouslyRefundedAuto
| DisputeUnifiedStatusCode::McCollaborationRefundedByMerchant
| DisputeUnifiedStatusCode::McCollaborationExpired
| DisputeUnifiedStatusCode::McCollaborationRejectedByMerchant
| DisputeUnifiedStatusCode::McCollaborationAutomaticAccept
| DisputeUnifiedStatusCode::McCollaborationClosedMerchantFavour
| DisputeUnifiedStatusCode::McCollaborationClosedCardholderFavour
| DisputeUnifiedStatusCode::CreditChargebackAcceptedAutomatically => Self::Dispute,
// --- PreArbitration ---
DisputeUnifiedStatusCode::PreArbitrationInitiatedByIssuer
| DisputeUnifiedStatusCode::MerchantPreArbitrationAcceptedByIssuer
| DisputeUnifiedStatusCode::MerchantPreArbitrationRejectedByIssuer
| DisputeUnifiedStatusCode::MerchantPreArbitrationPartiallyAcceptedByIssuer
| DisputeUnifiedStatusCode::PreArbitrationClosedMerchantFavour
| DisputeUnifiedStatusCode::PreArbitrationClosedCardholderFavour
| DisputeUnifiedStatusCode::PreArbitrationAcceptedByMerchant
| DisputeUnifiedStatusCode::PreArbitrationPartiallyAcceptedByMerchant
| DisputeUnifiedStatusCode::PreArbitrationPartiallyAcceptedByMerchantExpired
| DisputeUnifiedStatusCode::PreArbitrationRejectedByMerchant
| DisputeUnifiedStatusCode::PreArbitrationRejectedByMerchantExpired
| DisputeUnifiedStatusCode::PreArbitrationAutomaticallyAcceptedByMerchant
| DisputeUnifiedStatusCode::PreArbitrationClosedRecall
| DisputeUnifiedStatusCode::RejectedPreArbAcceptedByMerchant
| DisputeUnifiedStatusCode::RejectedPreArbExpiredAutoAccepted => Self::PreArbitration,
// --- DisputeReversal ---
DisputeUnifiedStatusCode::CreditChargebackRecalledByIssuer => Self::DisputeReversal,
}
}
}