mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 21:37:41 +08:00
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:
@ -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(¬ification)
|
||||
.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> {
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user