Skip to content

Commit b83e044

Browse files
srujanchikkeChikke Srujanhyperswitch-bot[bot]
authored
fix(connector): Add network error message support for payment connectors (#7760)
Co-authored-by: Chikke Srujan <[email protected]> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
1 parent 989b2c3 commit b83e044

File tree

8 files changed

+162
-58
lines changed

8 files changed

+162
-58
lines changed

crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,7 @@ fn get_error_response_if_failure(
15981598
if utils::is_payment_failure(status) {
15991599
Some(get_error_response(
16001600
&info_response.error_information,
1601+
&info_response.processor_information,
16011602
&info_response.risk_information,
16021603
Some(status),
16031604
http_code,
@@ -1923,6 +1924,7 @@ impl<F>
19231924
Ok(Self {
19241925
response: Err(get_error_response(
19251926
&item.response.error_information,
1927+
&item.response.processor_information,
19261928
&risk_info,
19271929
Some(status),
19281930
item.http_code,
@@ -2140,6 +2142,7 @@ impl TryFrom<RefundsResponseRouterData<Execute, BankOfAmericaRefundResponse>>
21402142
Err(get_error_response(
21412143
&item.response.error_information,
21422144
&None,
2145+
&None,
21432146
None,
21442147
item.http_code,
21452148
item.response.id,
@@ -2227,6 +2230,7 @@ impl TryFrom<RefundsResponseRouterData<RSync, BankOfAmericaRsyncResponse>>
22272230
details: None,
22282231
}),
22292232
&None,
2233+
&None,
22302234
None,
22312235
item.http_code,
22322236
item.response.id.clone(),
@@ -2235,6 +2239,7 @@ impl TryFrom<RefundsResponseRouterData<RSync, BankOfAmericaRsyncResponse>>
22352239
Err(get_error_response(
22362240
&item.response.error_information,
22372241
&None,
2242+
&None,
22382243
None,
22392244
item.http_code,
22402245
item.response.id.clone(),
@@ -2323,6 +2328,7 @@ pub struct AuthenticationErrorInformation {
23232328

23242329
fn get_error_response(
23252330
error_data: &Option<BankOfAmericaErrorInformation>,
2331+
processor_information: &Option<ClientProcessorInformation>,
23262332
risk_information: &Option<ClientRiskInformation>,
23272333
attempt_status: Option<enums::AttemptStatus>,
23282334
status_code: u16,
@@ -2354,6 +2360,14 @@ fn get_error_response(
23542360
.join(", ")
23552361
})
23562362
});
2363+
let network_decline_code = processor_information
2364+
.as_ref()
2365+
.and_then(|info| info.response_code.clone());
2366+
let network_advice_code = processor_information.as_ref().and_then(|info| {
2367+
info.merchant_advice
2368+
.as_ref()
2369+
.and_then(|merchant_advice| merchant_advice.code_raw.clone())
2370+
});
23572371

23582372
let reason = get_error_reason(
23592373
error_data
@@ -2377,8 +2391,8 @@ fn get_error_response(
23772391
status_code,
23782392
attempt_status,
23792393
connector_transaction_id: Some(transaction_id.clone()),
2380-
network_advice_code: None,
2381-
network_decline_code: None,
2394+
network_advice_code,
2395+
network_decline_code,
23822396
network_error_message: None,
23832397
}
23842398
}

crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -2629,6 +2629,15 @@ pub struct ClientProcessorInformation {
26292629
network_transaction_id: Option<String>,
26302630
avs: Option<Avs>,
26312631
card_verification: Option<CardVerification>,
2632+
merchant_advice: Option<MerchantAdvice>,
2633+
response_code: Option<String>,
2634+
}
2635+
2636+
#[derive(Debug, Clone, Deserialize, Serialize)]
2637+
#[serde(rename_all = "camelCase")]
2638+
pub struct MerchantAdvice {
2639+
code: Option<String>,
2640+
code_raw: Option<String>,
26322641
}
26332642

26342643
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -2675,6 +2684,7 @@ fn get_error_response_if_failure(
26752684
if utils::is_payment_failure(status) {
26762685
Some(get_error_response(
26772686
&info_response.error_information,
2687+
&info_response.processor_information,
26782688
&info_response.risk_information,
26792689
Some(status),
26802690
http_code,
@@ -3178,6 +3188,7 @@ impl<F>
31783188
if utils::is_payment_failure(status) {
31793189
let response = Err(get_error_response(
31803190
&info_response.error_information,
3191+
&None,
31813192
&risk_info,
31823193
Some(status),
31833194
item.http_code,
@@ -3543,6 +3554,7 @@ impl<F, T>
35433554
pub struct CybersourceTransactionResponse {
35443555
id: String,
35453556
application_information: ApplicationInformation,
3557+
processor_information: Option<ClientProcessorInformation>,
35463558
client_reference_information: Option<ClientReferenceInformation>,
35473559
error_information: Option<CybersourceErrorInformation>,
35483560
}
@@ -3583,6 +3595,7 @@ impl<F>
35833595
Ok(Self {
35843596
response: Err(get_error_response(
35853597
&item.response.error_information,
3598+
&item.response.processor_information,
35863599
&risk_info,
35873600
Some(status),
35883601
item.http_code,
@@ -3701,6 +3714,7 @@ impl TryFrom<RefundsResponseRouterData<Execute, CybersourceRefundResponse>>
37013714
Err(get_error_response(
37023715
&item.response.error_information,
37033716
&None,
3717+
&None,
37043718
None,
37053719
item.http_code,
37063720
item.response.id.clone(),
@@ -3756,6 +3770,7 @@ impl TryFrom<RefundsResponseRouterData<RSync, CybersourceRsyncResponse>>
37563770
details: None,
37573771
}),
37583772
&None,
3773+
&None,
37593774
None,
37603775
item.http_code,
37613776
item.response.id.clone(),
@@ -3764,6 +3779,7 @@ impl TryFrom<RefundsResponseRouterData<RSync, CybersourceRsyncResponse>>
37643779
Err(get_error_response(
37653780
&item.response.error_information,
37663781
&None,
3782+
&None,
37673783
None,
37683784
item.http_code,
37693785
item.response.id.clone(),
@@ -4097,6 +4113,7 @@ pub struct AuthenticationErrorInformation {
40974113

40984114
pub fn get_error_response(
40994115
error_data: &Option<CybersourceErrorInformation>,
4116+
processor_information: &Option<ClientProcessorInformation>,
41004117
risk_information: &Option<ClientRiskInformation>,
41014118
attempt_status: Option<enums::AttemptStatus>,
41024119
status_code: u16,
@@ -4128,6 +4145,14 @@ pub fn get_error_response(
41284145
.join(", ")
41294146
})
41304147
});
4148+
let network_decline_code = processor_information
4149+
.as_ref()
4150+
.and_then(|info| info.response_code.clone());
4151+
let network_advice_code = processor_information.as_ref().and_then(|info| {
4152+
info.merchant_advice
4153+
.as_ref()
4154+
.and_then(|merchant_advice| merchant_advice.code_raw.clone())
4155+
});
41314156

41324157
let reason = get_error_reason(
41334158
error_data
@@ -4151,8 +4176,8 @@ pub fn get_error_response(
41514176
status_code,
41524177
attempt_status,
41534178
connector_transaction_id: Some(transaction_id),
4154-
network_advice_code: None,
4155-
network_decline_code: None,
4179+
network_advice_code,
4180+
network_decline_code,
41564181
network_error_message: None,
41574182
}
41584183
}

crates/hyperswitch_connectors/src/connectors/globalpay/response.rs

+2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ pub struct PaymentMethod {
134134
/// Result message from the payment method provider corresponding to the result code.
135135
pub message: Option<String>,
136136
/// Result code from the payment method provider.
137+
/// If a card authorization declines, the payment_method result and message include more detail from the Issuer on why it was declined.
138+
/// For example, 51 - INSUFFICIENT FUNDS. This is generated by the issuing bank, who will provide decline codes in the response back to the authorization platform.
137139
pub result: Option<String>,
138140
}
139141

crates/hyperswitch_connectors/src/connectors/globalpay/transformers.rs

+31-5
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ use hyperswitch_domain_models::{
1919
PaymentsSyncRouterData, RefreshTokenRouterData, RefundExecuteRouterData, RefundsRouterData,
2020
},
2121
};
22-
use hyperswitch_interfaces::{consts::NO_ERROR_MESSAGE, errors};
22+
use hyperswitch_interfaces::{
23+
consts::{self, NO_ERROR_MESSAGE},
24+
errors,
25+
};
2326
use masking::{ExposeInterface, PeekInterface, Secret};
2427
use rand::distributions::DistString;
2528
use serde::{Deserialize, Serialize};
@@ -279,6 +282,7 @@ fn get_payment_response(
279282
status: common_enums::AttemptStatus,
280283
response: GlobalpayPaymentsResponse,
281284
redirection_data: Option<RedirectForm>,
285+
status_code: u16,
282286
) -> Result<PaymentsResponseData, Box<ErrorResponse>> {
283287
let mandate_reference = response.payment_method.as_ref().and_then(|pm| {
284288
pm.card
@@ -295,12 +299,33 @@ fn get_payment_response(
295299
common_enums::AttemptStatus::Failure => Err(Box::new(ErrorResponse {
296300
message: response
297301
.payment_method
298-
.and_then(|pm| pm.message)
302+
.as_ref()
303+
.and_then(|payment_method| payment_method.message.clone())
299304
.unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
300-
..Default::default()
305+
code: response
306+
.payment_method
307+
.as_ref()
308+
.and_then(|payment_method| payment_method.result.clone())
309+
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
310+
reason: response
311+
.payment_method
312+
.as_ref()
313+
.and_then(|payment_method| payment_method.message.clone()),
314+
status_code,
315+
attempt_status: Some(status),
316+
connector_transaction_id: Some(response.id),
317+
network_decline_code: response
318+
.payment_method
319+
.as_ref()
320+
.and_then(|payment_method| payment_method.result.clone()),
321+
network_advice_code: None,
322+
network_error_message: response
323+
.payment_method
324+
.as_ref()
325+
.and_then(|payment_method| payment_method.message.clone()),
301326
})),
302327
_ => Ok(PaymentsResponseData::TransactionResponse {
303-
resource_id: ResponseId::ConnectorTransactionId(response.id),
328+
resource_id: ResponseId::ConnectorTransactionId(response.id.clone()),
304329
redirection_data: Box::new(redirection_data),
305330
mandate_reference: Box::new(mandate_reference),
306331
connector_metadata: None,
@@ -336,9 +361,10 @@ impl<F, T> TryFrom<ResponseRouterData<F, GlobalpayPaymentsResponse, T, PaymentsR
336361
})
337362
.transpose()?;
338363
let redirection_data = redirect_url.map(|url| RedirectForm::from((url, Method::Get)));
364+
let status_code = item.http_code;
339365
Ok(Self {
340366
status,
341-
response: get_payment_response(status, item.response, redirection_data)
367+
response: get_payment_response(status, item.response, redirection_data, status_code)
342368
.map_err(|err| *err),
343369
..item.data
344370
})

crates/hyperswitch_connectors/src/connectors/worldpay/response.rs

+7
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,18 @@ pub struct FraudHighRiskResponse {
6565
#[serde(rename_all = "camelCase")]
6666
pub struct RefusedResponse {
6767
pub refusal_description: String,
68+
// Access Worldpay returns a raw response code in the refusalCode field (if enabled) containing the unmodified response code received either directly from the card scheme for Worldpay-acquired transactions, or from third party acquirers.
6869
pub refusal_code: String,
6970
pub risk_factors: Option<Vec<RiskFactorsInner>>,
7071
pub fraud: Option<Fraud>,
7172
#[serde(rename = "threeDS")]
7273
pub three_ds: Option<ThreeDsResponse>,
74+
pub advice: Option<Advice>,
75+
}
76+
77+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
78+
pub struct Advice {
79+
pub code: Option<String>,
7380
}
7481

7582
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]

crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs

+15-7
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,13 @@ impl<F, T>
748748
None,
749749
None,
750750
None,
751-
Some((res.refusal_code.clone(), res.refusal_description.clone())),
751+
Some((
752+
res.refusal_code.clone(),
753+
res.refusal_description.clone(),
754+
res.advice
755+
.as_ref()
756+
.and_then(|advice_code| advice_code.code.clone()),
757+
)),
752758
),
753759
WorldpayPaymentResponseFields::FraudHighRisk(_) => (None, None, None, None, None),
754760
})
@@ -790,16 +796,18 @@ impl<F, T>
790796
network_decline_code: None,
791797
network_error_message: None,
792798
}),
793-
(_, Some((code, message))) => Err(ErrorResponse {
794-
code,
799+
(_, Some((code, message, advice_code))) => Err(ErrorResponse {
800+
code: code.clone(),
795801
message: message.clone(),
796-
reason: Some(message),
802+
reason: Some(message.clone()),
797803
status_code: router_data.http_code,
798804
attempt_status: Some(status),
799805
connector_transaction_id: optional_correlation_id,
800-
network_advice_code: None,
801-
network_decline_code: None,
802-
network_error_message: None,
806+
network_advice_code: advice_code,
807+
// Access Worldpay returns a raw response code in the refusalCode field (if enabled) containing the unmodified response code received either directly from the card scheme for Worldpay-acquired transactions, or from third party acquirers.
808+
// You can use raw response codes to inform your retry logic. A rawCode is only returned if specifically requested.
809+
network_decline_code: Some(code),
810+
network_error_message: Some(message),
803811
}),
804812
};
805813
Ok(Self {

0 commit comments

Comments
 (0)