feat(connector): [ACI] cypress added (#9502)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
sweta-sharma
2025-09-29 12:42:14 +05:30
committed by GitHub
parent e03c0096d3
commit 1c52f69910
4 changed files with 385 additions and 82 deletions

View File

@ -1,4 +1,4 @@
pub(super) const FAILURE_CODES: [&str; 501] = [
pub(super) const FAILURE_CODES: [&str; 502] = [
"100.370.100",
"100.370.110",
"100.370.111",
@ -500,6 +500,7 @@ pub(super) const FAILURE_CODES: [&str; 501] = [
"800.800.102",
"800.800.202",
"800.800.302",
"100.390.100",
];
pub(super) const SUCCESSFUL_CODES: [&str; 16] = [

View File

@ -136,7 +136,8 @@ pub struct AciCancelRequest {
#[serde(rename_all = "camelCase")]
pub struct AciMandateRequest {
pub entity_id: Secret<String>,
pub payment_brand: PaymentBrand,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_brand: Option<PaymentBrand>,
#[serde(flatten)]
pub payment_details: PaymentDetails,
}
@ -395,7 +396,7 @@ impl TryFrom<(Card, Option<Secret<String>>)> for PaymentDetails {
) -> Result<Self, Self::Error> {
let card_expiry_year = card_data.get_expiry_year_4_digit();
let payment_brand = get_aci_payment_brand(card_data.card_network, false)?;
let payment_brand = get_aci_payment_brand(card_data.card_network, false).ok();
Ok(Self::AciCard(Box::new(CardDetails {
card_number: card_data.card_number,
@ -541,7 +542,8 @@ pub struct CardDetails {
#[serde(rename = "card.cvv")]
pub card_cvv: Secret<String>,
#[serde(rename = "paymentBrand")]
pub payment_brand: PaymentBrand,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_brand: Option<PaymentBrand>,
}
#[derive(Debug, Clone, Serialize)]
@ -879,16 +881,20 @@ impl TryFrom<&RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponse
let (payment_brand, payment_details) = match &item.request.payment_method_data {
PaymentMethodData::Card(card_data) => {
let brand = get_aci_payment_brand(card_data.card_network.clone(), false)?;
match brand {
PaymentBrand::Visa
| PaymentBrand::Mastercard
| PaymentBrand::AmericanExpress => {}
_ => Err(errors::ConnectorError::NotSupported {
message: "Payment method not supported for mandate setup".to_string(),
connector: "ACI",
})?,
}
let brand = get_aci_payment_brand(card_data.card_network.clone(), false).ok();
match brand.as_ref() {
Some(PaymentBrand::Visa)
| Some(PaymentBrand::Mastercard)
| Some(PaymentBrand::AmericanExpress) => (),
Some(_) => {
return Err(errors::ConnectorError::NotSupported {
message: "Payment method not supported for mandate setup".to_string(),
connector: "ACI",
}
.into());
}
None => (),
};
let details = PaymentDetails::AciCard(Box::new(CardDetails {
card_number: card_data.card_number.clone(),
@ -1150,13 +1156,13 @@ pub struct AciCaptureResponse {
currency: String,
descriptor: String,
result: AciCaptureResult,
result_details: AciCaptureResultDetails,
result_details: Option<AciCaptureResultDetails>,
build_number: String,
timestamp: String,
ndc: Secret<String>,
source: Secret<String>,
payment_method: String,
short_id: String,
source: Option<Secret<String>>,
payment_method: Option<String>,
short_id: Option<String>,
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
@ -1171,22 +1177,22 @@ pub struct AciCaptureResult {
pub struct AciCaptureResultDetails {
extended_description: String,
#[serde(rename = "clearingInstituteName")]
clearing_institute_name: String,
connector_tx_i_d1: String,
connector_tx_i_d3: String,
connector_tx_i_d2: String,
acquirer_response: String,
clearing_institute_name: Option<String>,
connector_tx_i_d1: Option<String>,
connector_tx_i_d3: Option<String>,
connector_tx_i_d2: Option<String>,
acquirer_response: Option<String>,
}
#[derive(Debug, Default, Clone, Deserialize)]
pub enum AciCaptureStatus {
pub enum AciStatus {
Succeeded,
Failed,
#[default]
Pending,
}
impl FromStr for AciCaptureStatus {
impl FromStr for AciStatus {
type Err = error_stack::Report<errors::ConnectorError>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if FAILURE_CODES.contains(&s) {
@ -1203,11 +1209,11 @@ impl FromStr for AciCaptureStatus {
}
}
fn map_aci_capture_status(item: AciCaptureStatus) -> enums::AttemptStatus {
fn map_aci_capture_status(item: AciStatus) -> enums::AttemptStatus {
match item {
AciCaptureStatus::Succeeded => enums::AttemptStatus::Charged,
AciCaptureStatus::Failed => enums::AttemptStatus::Failure,
AciCaptureStatus::Pending => enums::AttemptStatus::Pending,
AciStatus::Succeeded => enums::AttemptStatus::Charged,
AciStatus::Failed => enums::AttemptStatus::Failure,
AciStatus::Pending => enums::AttemptStatus::Pending,
}
}
@ -1218,8 +1224,7 @@ impl<F, T> TryFrom<ResponseRouterData<F, AciCaptureResponse, T, PaymentsResponse
fn try_from(
item: ResponseRouterData<F, AciCaptureResponse, T, PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let status =
map_aci_capture_status(AciCaptureStatus::from_str(&item.response.result.code)?);
let status = map_aci_capture_status(AciStatus::from_str(&item.response.result.code)?);
let response = if status == enums::AttemptStatus::Failure {
Err(ErrorResponse {
code: item.response.result.code.clone(),
@ -1254,6 +1259,69 @@ impl<F, T> TryFrom<ResponseRouterData<F, AciCaptureResponse, T, PaymentsResponse
}
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AciVoidResponse {
id: String,
referenced_id: String,
payment_type: AciPaymentType,
amount: StringMajorUnit,
currency: String,
descriptor: String,
result: AciCaptureResult,
result_details: Option<AciCaptureResultDetails>,
build_number: String,
timestamp: String,
ndc: Secret<String>,
}
fn map_aci_void_status(item: AciStatus) -> enums::AttemptStatus {
match item {
AciStatus::Succeeded => enums::AttemptStatus::Voided,
AciStatus::Failed => enums::AttemptStatus::VoidFailed,
AciStatus::Pending => enums::AttemptStatus::VoidInitiated,
}
}
impl<F, T> TryFrom<ResponseRouterData<F, AciVoidResponse, T, PaymentsResponseData>>
for RouterData<F, T, PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: ResponseRouterData<F, AciVoidResponse, T, PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let status = map_aci_void_status(AciStatus::from_str(&item.response.result.code)?);
let response = if status == enums::AttemptStatus::Failure {
Err(ErrorResponse {
code: item.response.result.code.clone(),
message: item.response.result.description.clone(),
reason: Some(item.response.result.description),
status_code: item.http_code,
attempt_status: Some(status),
connector_transaction_id: Some(item.response.id.clone()),
..Default::default()
})
} else {
Ok(PaymentsResponseData::TransactionResponse {
resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()),
redirection_data: Box::new(None),
mandate_reference: Box::new(None),
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: Some(item.response.referenced_id.clone()),
incremental_authorization_allowed: None,
charges: None,
})
};
Ok(Self {
status,
response,
reference_id: Some(item.response.referenced_id),
..item.data
})
}
}
#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AciRefundRequest {

View File

@ -2,27 +2,27 @@ import { customerAcceptance } from "./Commons";
const successfulNo3DSCardDetails = {
card_number: "4242424242424242",
card_exp_month: "10",
card_exp_year: "2050",
card_exp_month: "01",
card_exp_year: "2045",
card_holder_name: "morino",
card_cvc: "737",
};
const successfulThreeDSTestCardDetails = {
card_number: "4111111111111111",
card_exp_month: "10",
card_exp_year: "2050",
card_number: "5386024192625914",
card_exp_month: "01",
card_exp_year: "2045",
card_holder_name: "morino",
card_cvc: "737",
};
// This card details will fail because of card expiryYear
const failedNo3DSCardDetails = {
card_number: "4242424242424242",
card_number: "4012001037461114",
card_exp_month: "01",
card_exp_year: "35",
card_holder_name: "joseph Doe",
card_cvc: "123",
card_cvc: "737",
};
const singleUseMandateData = {
@ -30,7 +30,7 @@ const singleUseMandateData = {
mandate_type: {
single_use: {
amount: 8000,
currency: "USD",
currency: "EUR",
},
},
};
@ -40,7 +40,7 @@ const multiUseMandateData = {
mandate_type: {
multi_use: {
amount: 8000,
currency: "USD",
currency: "EUR",
},
},
};
@ -67,7 +67,7 @@ export const connectorDetails = {
card_pm: {
PaymentIntent: {
Request: {
currency: "USD",
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "on_session",
},
@ -79,13 +79,28 @@ export const connectorDetails = {
},
},
},
PaymentIntentOffSession: {
Request: {
amount: 6000,
authentication_type: "no_three_ds",
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "off_session",
},
Response: {
status: 200,
body: {
status: "requires_payment_method",
},
},
},
No3DSAutoCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "USD",
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "on_session",
},
@ -98,31 +113,62 @@ export const connectorDetails = {
},
},
},
No3DSManualCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "on_session",
},
Response: {
status: 200,
body: {
status: "requires_capture",
payment_method: "card",
attempt_count: 1,
},
},
},
"3DSAutoCapture": {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
card: successfulThreeDSTestCardDetails,
},
currency: "USD",
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "on_session",
},
Response: {
status: 200,
body: {
status: "succeeded",
payment_method: "card",
attempt_count: 1,
status: "requires_customer_action",
},
},
},
"3DSManualCapture": {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "on_session",
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
PaymentIntentWithShippingCost: {
Request: {
currency: "USD",
currency: "EUR",
shipping_cost: 50,
amount: 6000,
},
@ -139,7 +185,7 @@ export const connectorDetails = {
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "USD",
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "on_session",
},
@ -194,6 +240,17 @@ export const connectorDetails = {
},
},
},
PartialCapture: {
Request: {
amount_to_capture: 2000,
},
Response: {
status: 200,
body: {
status: "partially_captured",
},
},
},
manualPaymentRefund: {
Request: {
amount: 6000,
@ -225,7 +282,7 @@ export const connectorDetails = {
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "USD",
currency: "EUR",
mandate_data: singleUseMandateData,
},
Response: {
@ -235,6 +292,25 @@ export const connectorDetails = {
},
},
},
MandateSingleUse3DSManualCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
mandate_data: singleUseMandateData,
},
Response: {
status: 200,
body: {
status: "requires_capture",
},
},
},
MandateSingleUseNo3DSAutoCapture: {
Configs: {
TRIGGER_SKIP: true,
@ -244,7 +320,7 @@ export const connectorDetails = {
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "USD",
currency: "EUR",
mandate_data: singleUseMandateData,
},
Response: {
@ -254,7 +330,45 @@ export const connectorDetails = {
},
},
},
MandateSingleUseNo3DSManualCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "USD",
mandate_data: singleUseMandateData,
},
Response: {
status: 200,
body: {
status: "requires_capture",
},
},
},
MandateMultiUseNo3DSAutoCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
mandate_data: multiUseMandateData,
},
Response: {
status: 200,
body: {
status: "succeeded",
},
},
},
MandateMultiUseNo3DSManualCapture: {
Configs: {
TRIGGER_SKIP: true,
},
@ -269,7 +383,7 @@ export const connectorDetails = {
Response: {
status: 200,
body: {
status: "succeeded",
status: "requires_capture",
},
},
},
@ -282,7 +396,7 @@ export const connectorDetails = {
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "USD",
currency: "EUR",
mandate_data: multiUseMandateData,
},
Response: {
@ -292,15 +406,38 @@ export const connectorDetails = {
},
},
},
ZeroAuthMandate: {
MandateMultiUse3DSManualCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
mandate_data: multiUseMandateData,
},
Response: {
status: 501,
status: 200,
body: {
error: {
type: "invalid_request",
message: "Setup Mandate flow for Aci is not implemented",
code: "IR_00",
},
status: "requires_capture",
},
},
},
ZeroAuthMandate: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
mandate_data: singleUseMandateData,
},
Response: {
status: 200,
body: {
status: "succeeded",
},
},
},
@ -308,7 +445,7 @@ export const connectorDetails = {
Request: {
amount: 0,
setup_future_usage: "off_session",
currency: "USD",
currency: "EUR",
},
Response: {
status: 200,
@ -328,13 +465,9 @@ export const connectorDetails = {
},
},
Response: {
status: 501,
status: 200,
body: {
error: {
type: "invalid_request",
message: "Setup Mandate flow for Aci is not implemented",
code: "IR_00",
},
status: "succeeded",
},
},
},
@ -344,7 +477,7 @@ export const connectorDetails = {
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "USD",
currency: "EUR",
setup_future_usage: "on_session",
customer_acceptance: customerAcceptance,
},
@ -355,6 +488,23 @@ export const connectorDetails = {
},
},
},
SaveCardUseNo3DSManualCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
setup_future_usage: "on_session",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_capture",
},
},
},
SaveCardUseNo3DSAutoCaptureOffSession: {
Configs: {
TRIGGER_SKIP: true,
@ -375,6 +525,25 @@ export const connectorDetails = {
},
},
},
SaveCardUseNo3DSManualCaptureOffSession: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
setup_future_usage: "off_session",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_capture",
},
},
},
SaveCardUse3DSAutoCaptureOffSession: {
Configs: {
TRIGGER_SKIP: true,
@ -407,15 +576,12 @@ export const connectorDetails = {
},
},
PaymentMethodIdMandateNo3DSAutoCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "USD",
currency: "EUR",
mandate_data: null,
customer_acceptance: customerAcceptance,
},
@ -426,20 +592,74 @@ export const connectorDetails = {
},
},
},
PaymentMethodIdMandate3DSAutoCapture: {
Configs: {
TRIGGER_SKIP: true,
PaymentMethodIdMandateNo3DSManualCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
mandate_data: null,
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_capture",
},
},
},
PaymentMethodIdMandate3DSAutoCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "USD",
currency: "EUR",
mandate_data: null,
authentication_type: "three_ds",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
PaymentMethodIdMandate3DSManualCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
mandate_data: null,
authentication_type: "three_ds",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
MITManualCapture: {
Request: {
currency: "EUR",
},
Response: {
status: 200,
body: {
status: "requires_capture",
},
},
},
MITAutoCapture: {
Request: {
currency: "EUR",
},
Response: {
status: 200,
body: {
@ -448,6 +668,9 @@ export const connectorDetails = {
},
},
No3DSFailPayment: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
@ -460,9 +683,8 @@ export const connectorDetails = {
status: 200,
body: {
status: "failed",
error_code: "200.300.404",
error_message:
"Field is card.expiryYear and the message is must match ^[0-9]{4}$",
error_code: "100.390.112",
error_message: "Technical Error in 3D system",
unified_code: "UE_9000",
unified_message: "Something went wrong",
},

View File

@ -900,6 +900,18 @@ function threeDsRedirection(redirectionUrl, expectedUrl, connectorId) {
connectorId,
({ connectorId, constants, expectedUrl }) => {
switch (connectorId) {
case "aci":
cy.get('form[name="challengeForm"]', {
timeout: constants.WAIT_TIME,
})
.should("exist")
.then(() => {
cy.get("#outcomeSelect")
.select("Approve")
.should("have.value", "Y");
cy.get('button[type="submit"]').click();
});
break;
case "adyen":
cy.get("iframe")
.its("0.contentDocument.body")