mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(connector): [Nuvei] add support for bank redirect Eps, Sofort, Giropay, Ideal (#870)
Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
157
Cargo.lock
generated
157
Cargo.lock
generated
@ -226,7 +226,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"smallvec",
|
||||
"socket2",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"url",
|
||||
]
|
||||
|
||||
@ -338,7 +338,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"url",
|
||||
"utoipa",
|
||||
]
|
||||
@ -550,7 +550,7 @@ dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"ring",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tracing",
|
||||
@ -709,7 +709,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"regex",
|
||||
"sha2",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@ -816,7 +816,7 @@ dependencies = [
|
||||
"itoa",
|
||||
"num-integer",
|
||||
"ryu",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1099,9 +1099,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.43",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@ -1191,7 +1194,7 @@ dependencies = [
|
||||
"signal-hook",
|
||||
"signal-hook-tokio",
|
||||
"thiserror",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@ -1242,7 +1245,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
@ -1482,7 +1485,7 @@ dependencies = [
|
||||
"pq-sys",
|
||||
"r2d2",
|
||||
"serde_json",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1662,6 +1665,28 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fantoccini"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65f0fbe245d714b596ba5802b46f937f5ce68dcae0f32f9a70b5c3b04d3c6f64"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"cookie",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"mime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
"url",
|
||||
"webdriver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
@ -2308,7 +2333,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3486,8 +3511,9 @@ dependencies = [
|
||||
"signal-hook-tokio",
|
||||
"storage_models",
|
||||
"strum",
|
||||
"thirtyfour",
|
||||
"thiserror",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
"toml 0.7.3",
|
||||
"url",
|
||||
@ -3526,7 +3552,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"strum",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-actix-web",
|
||||
@ -3812,6 +3838,17 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
@ -3846,7 +3883,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3977,7 +4014,7 @@ dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4046,7 +4083,16 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum",
|
||||
"thiserror",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringmatch"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aadc0801d92f0cdc26127c67c4b8766284f52a5ba22894f285e3101fa57d05d"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4139,6 +4185,44 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thirtyfour"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72fc70ad9624071cdd96d034676b84b504bfeb4bee1580df1324c99373ea0ca7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.13.1",
|
||||
"chrono",
|
||||
"cookie",
|
||||
"fantoccini",
|
||||
"futures",
|
||||
"http",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"stringmatch",
|
||||
"thirtyfour-macros",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
"urlparse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thirtyfour-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cae91d1c7c61ec65817f1064954640ee350a50ae6548ff9a1bdd2489d6ffbb0"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
@ -4169,6 +4253,16 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.20"
|
||||
@ -4438,7 +4532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
@ -4589,6 +4683,12 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
@ -4619,6 +4719,12 @@ version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||
|
||||
[[package]]
|
||||
name = "urlparse"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "110352d4e9076c67839003c7788d8604e24dcded13e0b375af3efaa8cf468517"
|
||||
|
||||
[[package]]
|
||||
name = "utoipa"
|
||||
version = "3.3.0"
|
||||
@ -4691,7 +4797,7 @@ dependencies = [
|
||||
"git2",
|
||||
"rustc_version",
|
||||
"rustversion",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4829,6 +4935,25 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webdriver"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9973cb72c8587d5ad5efdb91e663d36177dc37725e6c90ca86c626b0cc45c93f"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"http",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time 0.3.20",
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
|
||||
@ -628,6 +628,7 @@ pub enum WalletData {
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "snake_case"))]
|
||||
pub struct GooglePayWalletData {
|
||||
/// The type of payment method
|
||||
#[serde(rename = "type")]
|
||||
@ -659,6 +660,7 @@ pub struct MbWayRedirection {
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "snake_case"))]
|
||||
pub struct GooglePayPaymentMethodInfo {
|
||||
/// The name of the card network
|
||||
pub card_network: String,
|
||||
|
||||
@ -102,6 +102,7 @@ time = { version = "0.3.20", features = ["macros"] }
|
||||
tokio = "1.27.0"
|
||||
toml = "0.7.3"
|
||||
wiremock = "0.5"
|
||||
thirtyfour = "0.31.0"
|
||||
|
||||
[[bin]]
|
||||
name = "router"
|
||||
|
||||
@ -10,7 +10,7 @@ use ::common_utils::{
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use transformers as nuvei;
|
||||
|
||||
use super::utils::{self, to_boolean, RouterData};
|
||||
use super::utils::{self, RouterData};
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::{
|
||||
@ -486,8 +486,12 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
)
|
||||
.await?;
|
||||
router_data.session_token = resp.session_token;
|
||||
let (enrolled_for_3ds, related_transaction_id) = match router_data.auth_type {
|
||||
storage_models::enums::AuthenticationType::ThreeDs => {
|
||||
let (enrolled_for_3ds, related_transaction_id) =
|
||||
match (router_data.auth_type, router_data.payment_method) {
|
||||
(
|
||||
storage_models::enums::AuthenticationType::ThreeDs,
|
||||
storage_models::enums::PaymentMethod::Card,
|
||||
) => {
|
||||
let integ: Box<
|
||||
&(dyn ConnectorIntegration<
|
||||
InitPayment,
|
||||
@ -508,12 +512,15 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
payments::CallConnectorAction::Trigger,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
init_resp.request.enrolled_for_3ds,
|
||||
init_resp.request.related_transaction_id,
|
||||
)
|
||||
match init_resp.response {
|
||||
Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse {
|
||||
enrolled_v2,
|
||||
related_transaction_id,
|
||||
}) => (enrolled_v2, related_transaction_id),
|
||||
_ => (false, None),
|
||||
}
|
||||
storage_models::enums::AuthenticationType::NoThreeDs => (false, None),
|
||||
}
|
||||
_ => (false, None),
|
||||
};
|
||||
|
||||
router_data.request.enrolled_for_3ds = enrolled_for_3ds;
|
||||
@ -725,28 +732,12 @@ impl ConnectorIntegration<InitPayment, types::PaymentsAuthorizeData, types::Paym
|
||||
.response
|
||||
.parse_struct("NuveiPaymentsResponse")
|
||||
.switch()?;
|
||||
let response_data = types::RouterData::try_from(types::ResponseRouterData {
|
||||
response: response.clone(),
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
|
||||
let is_enrolled_for_3ds = response
|
||||
.clone()
|
||||
.payment_option
|
||||
.and_then(|po| po.card)
|
||||
.and_then(|c| c.three_d)
|
||||
.and_then(|t| t.v2supported)
|
||||
.map(to_boolean)
|
||||
.unwrap_or_default();
|
||||
Ok(types::RouterData {
|
||||
request: types::PaymentsAuthorizeData {
|
||||
enrolled_for_3ds: is_enrolled_for_3ds,
|
||||
related_transaction_id: response.transaction_id,
|
||||
..response_data.request
|
||||
},
|
||||
..response_data
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use api_models::payments;
|
||||
use common_utils::{
|
||||
crypto::{self, GenerateDigest},
|
||||
date_time, fp_utils,
|
||||
@ -10,12 +11,13 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils::{
|
||||
self, MandateData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, RouterData,
|
||||
self, AddressDetailsData, MandateData, PaymentsAuthorizeRequestData,
|
||||
PaymentsCancelRequestData, RouterData,
|
||||
},
|
||||
consts,
|
||||
core::errors,
|
||||
services,
|
||||
types::{self, api, storage::enums},
|
||||
types::{self, api, storage::enums, transformers::ForeignTryFrom},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Default, Deserialize)]
|
||||
@ -62,7 +64,7 @@ pub struct NuveiPaymentsRequest {
|
||||
pub merchant_site_id: String,
|
||||
pub client_request_id: String,
|
||||
pub amount: String,
|
||||
pub currency: String,
|
||||
pub currency: storage_models::enums::Currency,
|
||||
/// This ID uniquely identifies your consumer/user in your system.
|
||||
pub user_token_id: Option<Secret<String, Email>>,
|
||||
pub client_unique_id: String,
|
||||
@ -95,7 +97,7 @@ pub struct NuveiPaymentFlowRequest {
|
||||
pub merchant_site_id: String,
|
||||
pub client_request_id: String,
|
||||
pub amount: String,
|
||||
pub currency: String,
|
||||
pub currency: storage_models::enums::Currency,
|
||||
pub related_transaction_id: Option<String>,
|
||||
pub checksum: String,
|
||||
}
|
||||
@ -125,22 +127,65 @@ pub struct PaymentOption {
|
||||
pub billing_address: Option<BillingAddress>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NuveiBIC {
|
||||
#[serde(rename = "ABNANL2A")]
|
||||
Abnamro,
|
||||
#[serde(rename = "ASNBNL21")]
|
||||
ASNBank,
|
||||
#[serde(rename = "BUNQNL2A")]
|
||||
Bunq,
|
||||
#[serde(rename = "INGBNL2A")]
|
||||
Ing,
|
||||
#[serde(rename = "KNABNL2H")]
|
||||
Knab,
|
||||
#[serde(rename = "RABONL2U")]
|
||||
Rabobank,
|
||||
#[serde(rename = "RBRBNL21")]
|
||||
RegioBank,
|
||||
#[serde(rename = "SNSBNL2A")]
|
||||
SNSBank,
|
||||
#[serde(rename = "TRIONL2U")]
|
||||
TriodosBank,
|
||||
#[serde(rename = "FVLBNL22")]
|
||||
VanLanschotBankiers,
|
||||
#[serde(rename = "MOYONL21")]
|
||||
Moneyou,
|
||||
}
|
||||
|
||||
#[serde_with::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AlternativePaymentMethod {
|
||||
pub payment_method: AlternativePaymentMethodType,
|
||||
#[serde(rename = "BIC")]
|
||||
pub bank_id: Option<NuveiBIC>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AlternativePaymentMethodType {
|
||||
#[default]
|
||||
ApmgwExpresscheckout,
|
||||
#[serde(rename = "apmgw_expresscheckout")]
|
||||
Expresscheckout,
|
||||
#[serde(rename = "apmgw_Giropay")]
|
||||
Giropay,
|
||||
#[serde(rename = "apmgw_Sofort")]
|
||||
Sofort,
|
||||
#[serde(rename = "apmgw_iDeal")]
|
||||
Ideal,
|
||||
#[serde(rename = "apmgw_EPS")]
|
||||
Eps,
|
||||
}
|
||||
|
||||
#[serde_with::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BillingAddress {
|
||||
pub email: Secret<String, Email>,
|
||||
pub email: Option<Secret<String, Email>>,
|
||||
pub first_name: Option<Secret<String>>,
|
||||
pub last_name: Option<Secret<String>>,
|
||||
pub country: api_models::enums::CountryCode,
|
||||
}
|
||||
|
||||
@ -366,28 +411,34 @@ impl<F, T>
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NuveiCardDetails {
|
||||
card: api_models::payments::Card,
|
||||
card: payments::Card,
|
||||
three_d: Option<ThreeD>,
|
||||
}
|
||||
impl From<api_models::payments::GooglePayWalletData> for NuveiPaymentsRequest {
|
||||
fn from(gpay_data: api_models::payments::GooglePayWalletData) -> Self {
|
||||
Self {
|
||||
impl TryFrom<payments::GooglePayWalletData> for NuveiPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(gpay_data: payments::GooglePayWalletData) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
payment_option: PaymentOption {
|
||||
card: Some(Card {
|
||||
external_token: Some(ExternalToken {
|
||||
external_token_provider: ExternalTokenProvider::GooglePay,
|
||||
mobile_token: gpay_data.tokenization_data.token,
|
||||
mobile_token: common_utils::ext_traits::Encode::<
|
||||
payments::GooglePayWalletData,
|
||||
>::encode_to_string_of_json(
|
||||
&gpay_data
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?,
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<api_models::payments::ApplePayWalletData> for NuveiPaymentsRequest {
|
||||
fn from(apple_pay_data: api_models::payments::ApplePayWalletData) -> Self {
|
||||
impl From<payments::ApplePayWalletData> for NuveiPaymentsRequest {
|
||||
fn from(apple_pay_data: payments::ApplePayWalletData) -> Self {
|
||||
Self {
|
||||
payment_option: PaymentOption {
|
||||
card: Some(Card {
|
||||
@ -404,6 +455,109 @@ impl From<api_models::payments::ApplePayWalletData> for NuveiPaymentsRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<api_models::enums::BankNames> for NuveiBIC {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(bank: api_models::enums::BankNames) -> Result<Self, Self::Error> {
|
||||
match bank {
|
||||
api_models::enums::BankNames::AbnAmro => Ok(Self::Abnamro),
|
||||
api_models::enums::BankNames::AsnBank => Ok(Self::ASNBank),
|
||||
api_models::enums::BankNames::Bunq => Ok(Self::Bunq),
|
||||
api_models::enums::BankNames::Ing => Ok(Self::Ing),
|
||||
api_models::enums::BankNames::Knab => Ok(Self::Knab),
|
||||
api_models::enums::BankNames::Rabobank => Ok(Self::Rabobank),
|
||||
api_models::enums::BankNames::SnsBank => Ok(Self::SNSBank),
|
||||
api_models::enums::BankNames::TriodosBank => Ok(Self::TriodosBank),
|
||||
api_models::enums::BankNames::VanLanschot => Ok(Self::VanLanschotBankiers),
|
||||
api_models::enums::BankNames::Moneyou => Ok(Self::Moneyou),
|
||||
_ => Err(errors::ConnectorError::FlowNotSupported {
|
||||
flow: bank.to_string(),
|
||||
connector: "Nuvei".to_string(),
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F>
|
||||
ForeignTryFrom<(
|
||||
AlternativePaymentMethodType,
|
||||
Option<payments::BankRedirectData>,
|
||||
&types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||
)> for NuveiPaymentsRequest
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn foreign_try_from(
|
||||
data: (
|
||||
AlternativePaymentMethodType,
|
||||
Option<payments::BankRedirectData>,
|
||||
&types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let (payment_method, redirect, item) = data;
|
||||
let (billing_address, bank_id) = match (&payment_method, redirect) {
|
||||
(AlternativePaymentMethodType::Expresscheckout, _) => (
|
||||
Some(BillingAddress {
|
||||
email: Some(item.request.get_email()?),
|
||||
country: item.get_billing_country()?,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
),
|
||||
(AlternativePaymentMethodType::Giropay, _) => (
|
||||
Some(BillingAddress {
|
||||
email: Some(item.request.get_email()?),
|
||||
country: item.get_billing_country()?,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
),
|
||||
(AlternativePaymentMethodType::Sofort, _) | (AlternativePaymentMethodType::Eps, _) => {
|
||||
let address = item.get_billing_address()?;
|
||||
(
|
||||
Some(BillingAddress {
|
||||
first_name: Some(address.get_first_name()?.clone()),
|
||||
last_name: Some(address.get_last_name()?.clone()),
|
||||
email: Some(item.request.get_email()?),
|
||||
country: item.get_billing_country()?,
|
||||
}),
|
||||
None,
|
||||
)
|
||||
}
|
||||
(
|
||||
AlternativePaymentMethodType::Ideal,
|
||||
Some(payments::BankRedirectData::Ideal { bank_name, .. }),
|
||||
) => {
|
||||
let address = item.get_billing_address()?;
|
||||
(
|
||||
Some(BillingAddress {
|
||||
first_name: Some(address.get_first_name()?.clone()),
|
||||
last_name: Some(address.get_last_name()?.clone()),
|
||||
email: Some(item.request.get_email()?),
|
||||
country: item.get_billing_country()?,
|
||||
}),
|
||||
Some(NuveiBIC::try_from(bank_name)?),
|
||||
)
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotSupported {
|
||||
payment_method: "Bank Redirect".to_string(),
|
||||
connector: "Nuvei",
|
||||
payment_experience: "Redirection".to_string(),
|
||||
})?,
|
||||
};
|
||||
Ok(Self {
|
||||
payment_option: PaymentOption {
|
||||
alternative_payment_method: Some(AlternativePaymentMethod {
|
||||
payment_method,
|
||||
bank_id,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
billing_address,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F>
|
||||
TryFrom<(
|
||||
&types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||
@ -421,23 +575,13 @@ impl<F>
|
||||
let request_data = match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(card) => get_card_info(item, &card),
|
||||
api::PaymentMethodData::Wallet(wallet) => match wallet {
|
||||
api_models::payments::WalletData::GooglePay(gpay_data) => Ok(Self::from(gpay_data)),
|
||||
api_models::payments::WalletData::ApplePay(apple_pay_data) => {
|
||||
Ok(Self::from(apple_pay_data))
|
||||
}
|
||||
api_models::payments::WalletData::PaypalRedirect(_) => Ok(Self {
|
||||
payment_option: PaymentOption {
|
||||
alternative_payment_method: Some(AlternativePaymentMethod {
|
||||
payment_method: AlternativePaymentMethodType::ApmgwExpresscheckout,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
billing_address: Some(BillingAddress {
|
||||
email: item.request.get_email()?,
|
||||
country: item.get_billing_country()?,
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
payments::WalletData::GooglePay(gpay_data) => Self::try_from(gpay_data),
|
||||
payments::WalletData::ApplePay(apple_pay_data) => Ok(Self::from(apple_pay_data)),
|
||||
payments::WalletData::PaypalRedirect(_) => Self::foreign_try_from((
|
||||
AlternativePaymentMethodType::Expresscheckout,
|
||||
None,
|
||||
item,
|
||||
)),
|
||||
_ => Err(errors::ConnectorError::NotSupported {
|
||||
payment_method: "Wallet".to_string(),
|
||||
connector: "Nuvei",
|
||||
@ -445,11 +589,39 @@ impl<F>
|
||||
}
|
||||
.into()),
|
||||
},
|
||||
api::PaymentMethodData::BankRedirect(redirect) => match redirect {
|
||||
payments::BankRedirectData::Eps { .. } => Self::foreign_try_from((
|
||||
AlternativePaymentMethodType::Eps,
|
||||
Some(redirect),
|
||||
item,
|
||||
)),
|
||||
payments::BankRedirectData::Giropay { .. } => Self::foreign_try_from((
|
||||
AlternativePaymentMethodType::Giropay,
|
||||
Some(redirect),
|
||||
item,
|
||||
)),
|
||||
payments::BankRedirectData::Ideal { .. } => Self::foreign_try_from((
|
||||
AlternativePaymentMethodType::Ideal,
|
||||
Some(redirect),
|
||||
item,
|
||||
)),
|
||||
payments::BankRedirectData::Sofort { .. } => Self::foreign_try_from((
|
||||
AlternativePaymentMethodType::Sofort,
|
||||
Some(redirect),
|
||||
item,
|
||||
)),
|
||||
_ => Err(errors::ConnectorError::NotSupported {
|
||||
payment_method: "Bank Redirect".to_string(),
|
||||
connector: "Nuvei",
|
||||
payment_experience: "RedirectToUrl".to_string(),
|
||||
}
|
||||
.into()),
|
||||
},
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
||||
}?;
|
||||
let request = Self::try_from(NuveiPaymentRequestData {
|
||||
amount: item.request.amount.clone().to_string(),
|
||||
currency: item.request.currency.clone().to_string(),
|
||||
amount: utils::to_currency_base_unit(item.request.amount, item.request.currency)?,
|
||||
currency: item.request.currency,
|
||||
connector_auth_type: item.connector_auth_type.clone(),
|
||||
client_request_id: item.attempt_id.clone(),
|
||||
session_token: data.1,
|
||||
@ -461,6 +633,7 @@ impl<F>
|
||||
user_token_id: request_data.user_token_id,
|
||||
related_transaction_id: request_data.related_transaction_id,
|
||||
payment_option: request_data.payment_option,
|
||||
billing_address: request_data.billing_address,
|
||||
..request
|
||||
})
|
||||
}
|
||||
@ -468,7 +641,7 @@ impl<F>
|
||||
|
||||
fn get_card_info<F>(
|
||||
item: &types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||
card_details: &api_models::payments::Card,
|
||||
card_details: &payments::Card,
|
||||
) -> Result<NuveiPaymentsRequest, error_stack::Report<errors::ConnectorError>> {
|
||||
let browser_info = item.request.get_browser_info()?;
|
||||
let related_transaction_id = if item.is_three_ds() {
|
||||
@ -493,8 +666,8 @@ fn get_card_info<F>(
|
||||
match item.request.setup_mandate_details.clone() {
|
||||
Some(mandate_data) => {
|
||||
let details = match mandate_data.mandate_type {
|
||||
api_models::payments::MandateType::SingleUse(details) => details,
|
||||
api_models::payments::MandateType::MultiUse(details) => {
|
||||
payments::MandateType::SingleUse(details) => details,
|
||||
payments::MandateType::MultiUse(details) => {
|
||||
details.ok_or(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "mandate_data.mandate_type.multi_use",
|
||||
})?
|
||||
@ -593,8 +766,8 @@ impl TryFrom<(&types::PaymentsCompleteAuthorizeRouterData, String)> for NuveiPay
|
||||
)),
|
||||
}?;
|
||||
let request = Self::try_from(NuveiPaymentRequestData {
|
||||
amount: item.request.amount.clone().to_string(),
|
||||
currency: item.request.currency.clone().to_string(),
|
||||
amount: utils::to_currency_base_unit(item.request.amount, item.request.currency)?,
|
||||
currency: item.request.currency,
|
||||
connector_auth_type: item.connector_auth_type.clone(),
|
||||
client_request_id: item.attempt_id.clone(),
|
||||
session_token: data.1,
|
||||
@ -640,7 +813,7 @@ impl TryFrom<NuveiPaymentRequestData> for NuveiPaymentsRequest {
|
||||
merchant_site_id,
|
||||
client_request_id,
|
||||
request.amount.clone(),
|
||||
request.currency.clone(),
|
||||
request.currency.clone().to_string(),
|
||||
time_stamp,
|
||||
merchant_secret,
|
||||
])?,
|
||||
@ -673,7 +846,7 @@ impl TryFrom<NuveiPaymentRequestData> for NuveiPaymentFlowRequest {
|
||||
merchant_site_id,
|
||||
client_request_id,
|
||||
request.amount.clone(),
|
||||
request.currency.clone(),
|
||||
request.currency.clone().to_string(),
|
||||
request.related_transaction_id.clone().unwrap_or_default(),
|
||||
time_stamp,
|
||||
merchant_secret,
|
||||
@ -685,11 +858,10 @@ impl TryFrom<NuveiPaymentRequestData> for NuveiPaymentFlowRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Common request handler for all the flows that has below fields in common
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NuveiPaymentRequestData {
|
||||
pub amount: String,
|
||||
pub currency: String,
|
||||
pub currency: storage_models::enums::Currency,
|
||||
pub related_transaction_id: Option<String>,
|
||||
pub client_request_id: String,
|
||||
pub connector_auth_type: types::ConnectorAuthType,
|
||||
@ -704,7 +876,7 @@ impl TryFrom<&types::PaymentsCaptureRouterData> for NuveiPaymentFlowRequest {
|
||||
client_request_id: item.attempt_id.clone(),
|
||||
connector_auth_type: item.connector_auth_type.clone(),
|
||||
amount: item.request.amount_to_capture.to_string(),
|
||||
currency: item.request.currency.to_string(),
|
||||
currency: item.request.currency,
|
||||
related_transaction_id: Some(item.request.connector_transaction_id.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
@ -717,7 +889,7 @@ impl TryFrom<&types::RefundExecuteRouterData> for NuveiPaymentFlowRequest {
|
||||
client_request_id: item.attempt_id.clone(),
|
||||
connector_auth_type: item.connector_auth_type.clone(),
|
||||
amount: item.request.amount.to_string(),
|
||||
currency: item.request.currency.to_string(),
|
||||
currency: item.request.currency,
|
||||
related_transaction_id: Some(item.request.connector_transaction_id.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
@ -741,7 +913,7 @@ impl TryFrom<&types::PaymentsCancelRouterData> for NuveiPaymentFlowRequest {
|
||||
client_request_id: item.attempt_id.clone(),
|
||||
connector_auth_type: item.connector_auth_type.clone(),
|
||||
amount: item.request.get_amount()?.to_string(),
|
||||
currency: item.request.get_currency()?.to_string(),
|
||||
currency: item.request.get_currency()?,
|
||||
related_transaction_id: Some(item.request.connector_transaction_id.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
@ -868,14 +1040,11 @@ fn get_payment_status(response: &NuveiPaymentsResponse) -> enums::AttemptStatus
|
||||
NuveiTransactionStatus::Declined | NuveiTransactionStatus::Error => {
|
||||
match response.transaction_type {
|
||||
Some(NuveiTransactionType::Auth) => enums::AttemptStatus::AuthorizationFailed,
|
||||
Some(NuveiTransactionType::Sale) | Some(NuveiTransactionType::Settle) => {
|
||||
enums::AttemptStatus::Failure
|
||||
}
|
||||
Some(NuveiTransactionType::Void) => enums::AttemptStatus::VoidFailed,
|
||||
Some(NuveiTransactionType::Auth3D) => {
|
||||
enums::AttemptStatus::AuthenticationFailed
|
||||
}
|
||||
_ => enums::AttemptStatus::Pending,
|
||||
_ => enums::AttemptStatus::Failure,
|
||||
}
|
||||
}
|
||||
NuveiTransactionStatus::Processing => enums::AttemptStatus::Pending,
|
||||
@ -888,16 +1057,58 @@ fn get_payment_status(response: &NuveiPaymentsResponse) -> enums::AttemptStatus
|
||||
}
|
||||
}
|
||||
|
||||
fn build_error_response<T>(
|
||||
response: &NuveiPaymentsResponse,
|
||||
http_code: u16,
|
||||
) -> Option<Result<T, types::ErrorResponse>> {
|
||||
match response.status {
|
||||
NuveiPaymentStatus::Error => Some(get_error_response(
|
||||
response.err_code,
|
||||
&response.reason,
|
||||
http_code,
|
||||
)),
|
||||
_ => {
|
||||
let err = Some(get_error_response(
|
||||
response.gw_error_code,
|
||||
&response.gw_error_reason,
|
||||
http_code,
|
||||
));
|
||||
match response.transaction_status {
|
||||
Some(NuveiTransactionStatus::Error) => err,
|
||||
_ => match response
|
||||
.gw_error_reason
|
||||
.as_ref()
|
||||
.map(|r| r.eq("Missing argument"))
|
||||
{
|
||||
Some(true) => err,
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NuveiPaymentsGenericResponse {}
|
||||
|
||||
impl NuveiPaymentsGenericResponse for api::Authorize {}
|
||||
impl NuveiPaymentsGenericResponse for api::CompleteAuthorize {}
|
||||
impl NuveiPaymentsGenericResponse for api::Void {}
|
||||
impl NuveiPaymentsGenericResponse for api::PSync {}
|
||||
impl NuveiPaymentsGenericResponse for api::Capture {}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, NuveiPaymentsResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
where
|
||||
F: NuveiPaymentsGenericResponse,
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, NuveiPaymentsResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let redirection_data = match item.data.payment_method {
|
||||
storage_models::enums::PaymentMethod::Wallet => item
|
||||
storage_models::enums::PaymentMethod::Wallet
|
||||
| storage_models::enums::PaymentMethod::BankRedirect => item
|
||||
.response
|
||||
.payment_option
|
||||
.as_ref()
|
||||
@ -920,17 +1131,10 @@ impl<F, T>
|
||||
let response = item.response;
|
||||
Ok(Self {
|
||||
status: get_payment_status(&response),
|
||||
response: match response.status {
|
||||
NuveiPaymentStatus::Error => {
|
||||
get_error_response(response.err_code, response.reason, item.http_code)
|
||||
}
|
||||
_ => match response.transaction_status {
|
||||
Some(NuveiTransactionStatus::Error) => get_error_response(
|
||||
response.gw_error_code,
|
||||
response.gw_error_reason,
|
||||
item.http_code,
|
||||
),
|
||||
_ => Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
response: if let Some(err) = build_error_response(&response, item.http_code) {
|
||||
err
|
||||
} else {
|
||||
Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: response
|
||||
.transaction_id
|
||||
.map_or(response.order_id, Some) // For paypal there will be no transaction_id, only order_id will be present
|
||||
@ -952,9 +1156,35 @@ impl<F, T>
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
},
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<types::PaymentsInitResponseRouterData<NuveiPaymentsResponse>>
|
||||
for types::PaymentsInitRouterData
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::PaymentsInitResponseRouterData<NuveiPaymentsResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let response = item.response;
|
||||
let is_enrolled_for_3ds = response
|
||||
.clone()
|
||||
.payment_option
|
||||
.and_then(|po| po.card)
|
||||
.and_then(|c| c.three_d)
|
||||
.and_then(|t| t.v2supported)
|
||||
.map(utils::to_boolean)
|
||||
.unwrap_or_default();
|
||||
Ok(Self {
|
||||
status: get_payment_status(&response),
|
||||
response: Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse {
|
||||
enrolled_v2: is_enrolled_for_3ds,
|
||||
related_transaction_id: response.transaction_id,
|
||||
}),
|
||||
},
|
||||
},
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
@ -1022,11 +1252,11 @@ fn get_refund_response(
|
||||
.unwrap_or(enums::RefundStatus::Failure);
|
||||
match response.status {
|
||||
NuveiPaymentStatus::Error => {
|
||||
get_error_response(response.err_code, response.reason, http_code)
|
||||
get_error_response(response.err_code, &response.reason, http_code)
|
||||
}
|
||||
_ => match response.transaction_status {
|
||||
Some(NuveiTransactionStatus::Error) => {
|
||||
get_error_response(response.gw_error_code, response.gw_error_reason, http_code)
|
||||
get_error_response(response.gw_error_code, &response.gw_error_reason, http_code)
|
||||
}
|
||||
_ => Ok(types::RefundsResponseData {
|
||||
connector_refund_id: txn_id,
|
||||
@ -1038,14 +1268,16 @@ fn get_refund_response(
|
||||
|
||||
fn get_error_response<T>(
|
||||
error_code: Option<i64>,
|
||||
error_msg: Option<String>,
|
||||
error_msg: &Option<String>,
|
||||
http_code: u16,
|
||||
) -> Result<T, types::ErrorResponse> {
|
||||
Err(types::ErrorResponse {
|
||||
code: error_code
|
||||
.map(|c| c.to_string())
|
||||
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
|
||||
message: error_msg.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||
message: error_msg
|
||||
.clone()
|
||||
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||
reason: None,
|
||||
status_code: http_code,
|
||||
})
|
||||
|
||||
@ -117,8 +117,10 @@ impl types::PaymentsAuthorizeRouterData {
|
||||
.execute_pretasks(self, state)
|
||||
.await
|
||||
.map_err(|error| error.to_payment_failed_response())?;
|
||||
logger::debug!(completed_pre_tasks=?true);
|
||||
if self.should_proceed_with_authorize() {
|
||||
self.decide_authentication_type();
|
||||
logger::debug!(auth_type=?self.auth_type);
|
||||
let resp = services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration,
|
||||
|
||||
@ -387,6 +387,7 @@ async fn payment_response_update_tracker<F: Clone, T>(
|
||||
types::PaymentsResponseData::SessionResponse { .. } => (None, None),
|
||||
types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None),
|
||||
types::PaymentsResponseData::TokenizationResponse { .. } => (None, None),
|
||||
types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => (None, None),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -51,6 +51,8 @@ pub type PaymentsSyncResponseRouterData<R> =
|
||||
ResponseRouterData<api::PSync, R, PaymentsSyncData, PaymentsResponseData>;
|
||||
pub type PaymentsSessionResponseRouterData<R> =
|
||||
ResponseRouterData<api::Session, R, PaymentsSessionData, PaymentsResponseData>;
|
||||
pub type PaymentsInitResponseRouterData<R> =
|
||||
ResponseRouterData<api::InitPayment, R, PaymentsAuthorizeData, PaymentsResponseData>;
|
||||
pub type PaymentsCaptureResponseRouterData<R> =
|
||||
ResponseRouterData<api::Capture, R, PaymentsCaptureData, PaymentsResponseData>;
|
||||
pub type TokenizationResponseRouterData<R> = ResponseRouterData<
|
||||
@ -283,6 +285,10 @@ pub enum PaymentsResponseData {
|
||||
TokenizationResponse {
|
||||
token: String,
|
||||
},
|
||||
ThreeDSEnrollmentResponse {
|
||||
enrolled_v2: bool,
|
||||
related_transaction_id: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
||||
@ -18,11 +18,13 @@ mod mollie;
|
||||
mod multisafepay;
|
||||
mod nexinets;
|
||||
mod nuvei;
|
||||
mod nuvei_ui;
|
||||
mod opennode;
|
||||
mod payeezy;
|
||||
mod paypal;
|
||||
mod payu;
|
||||
mod rapyd;
|
||||
mod selenium;
|
||||
mod shift4;
|
||||
mod stripe;
|
||||
mod trustpay;
|
||||
|
||||
177
crates/router/tests/connectors/nuvei_ui.rs
Normal file
177
crates/router/tests/connectors/nuvei_ui.rs
Normal file
@ -0,0 +1,177 @@
|
||||
use serial_test::serial;
|
||||
use thirtyfour::{prelude::*, WebDriver};
|
||||
|
||||
use crate::{selenium::*, tester};
|
||||
|
||||
struct NuveiSeleniumTest;
|
||||
|
||||
impl SeleniumTest for NuveiSeleniumTest {}
|
||||
|
||||
async fn should_make_nuvei_3ds_payment(c: WebDriver) -> Result<(), WebDriverError> {
|
||||
let conn = NuveiSeleniumTest {};
|
||||
conn.make_redirection_payment(c, vec![
|
||||
Event::Trigger(Trigger::Goto("https://hs-payment-tests.w3spaces.com?pay-mode=pm-card&cname=CL-BRW1&ccnum=4000027891380961&expmonth=10&expyear=25&cvv=123&amount=200&country=US¤cy=USD")),
|
||||
Event::Assert(Assert::IsPresent("Exp Year")),
|
||||
Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
|
||||
Event::Trigger(Trigger::Query(By::ClassName("title"))),
|
||||
Event::Assert(Assert::Eq(Selector::Title, "ThreeDS ACS Emulator - Challenge Page")),
|
||||
Event::Trigger(Trigger::Click(By::Id("btn1"))),
|
||||
Event::Trigger(Trigger::Click(By::Id("btn5"))),
|
||||
Event::Assert(Assert::IsPresent("Google")),
|
||||
Event::Assert(Assert::Contains(Selector::QueryParamStr, "status=succeeded")),
|
||||
|
||||
]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_make_nuvei_3ds_mandate_payment(c: WebDriver) -> Result<(), WebDriverError> {
|
||||
let conn = NuveiSeleniumTest {};
|
||||
conn.make_redirection_payment(c, vec![
|
||||
Event::Trigger(Trigger::Goto("https://hs-payment-tests.w3spaces.com?pay-mode=pm-card&cname=CL-BRW1&ccnum=4000027891380961&expmonth=10&expyear=25&cvv=123&amount=200&country=US¤cy=USD&setup_future_usage=off_session&mandate_data[customer_acceptance][acceptance_type]=offline&mandate_data[customer_acceptance][accepted_at]=1963-05-03T04:07:52.723Z&mandate_data[customer_acceptance][online][ip_address]=in%20sit&mandate_data[customer_acceptance][online][user_agent]=amet%20irure%20esse&mandate_data[mandate_type][multi_use][amount]=7000&mandate_data[mandate_type][multi_use][currency]=USD&mandate_data[mandate_type][multi_use][start_date]=2022-09-10T00:00:00Z&mandate_data[mandate_type][multi_use][end_date]=2023-09-10T00:00:00Z&mandate_data[mandate_type][multi_use][metadata][frequency]=13")),
|
||||
Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
|
||||
Event::Trigger(Trigger::Query(By::ClassName("title"))),
|
||||
Event::Assert(Assert::Eq(Selector::Title, "ThreeDS ACS Emulator - Challenge Page")),
|
||||
Event::Trigger(Trigger::Click(By::Id("btn1"))),
|
||||
Event::Trigger(Trigger::Click(By::Id("btn5"))),
|
||||
Event::Assert(Assert::IsPresent("Google")),
|
||||
Event::Assert(Assert::Contains(Selector::QueryParamStr, "status=succeeded")),
|
||||
|
||||
]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_make_nuvei_gpay_payment(c: WebDriver) -> Result<(), WebDriverError> {
|
||||
let conn = NuveiSeleniumTest {};
|
||||
conn.make_gpay_payment(c,
|
||||
"https://hs-payment-tests.w3spaces.com?pay-mode=pm-gpay&gatewayname=nuveidigital&gatewaymerchantid=googletest&amount=10.00&country=IN¤cy=USD",
|
||||
vec![
|
||||
Event::Assert(Assert::IsPresent("succeeded")),
|
||||
]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_make_nuvei_pypl_payment(c: WebDriver) -> Result<(), WebDriverError> {
|
||||
let conn = NuveiSeleniumTest {};
|
||||
conn.make_paypal_payment(c,
|
||||
"https://hs-payment-tests.w3spaces.com?pay-mode=pypl-redirect&amount=12.00&country=US¤cy=USD",
|
||||
vec![
|
||||
Event::Assert(Assert::IsPresent("Your transaction has been successfully executed.")),
|
||||
]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_make_nuvei_giropay_payment(c: WebDriver) -> Result<(), WebDriverError> {
|
||||
let conn = NuveiSeleniumTest {};
|
||||
conn.make_redirection_payment(c, vec![
|
||||
Event::Trigger(Trigger::Goto("https://hs-payment-tests.w3spaces.com?pay-mode=bank-redirect&amount=1.00&country=DE¤cy=EUR&paymentmethod=giropay")),
|
||||
Event::Trigger(Trigger::Click(By::Id("bank-redirect-btn"))),
|
||||
Event::Assert(Assert::IsPresent("You are about to make a payment using the Giropay service.")),
|
||||
Event::Trigger(Trigger::Click(By::Id("ctl00_ctl00_mainContent_btnConfirm"))),
|
||||
Event::RunIf(Assert::IsPresent("Bank suchen"), vec![
|
||||
Event::Trigger(Trigger::SendKeys(By::Id("bankSearch"), "GIROPAY Testbank 1")),
|
||||
Event::Trigger(Trigger::Click(By::Id("GIROPAY Testbank 1"))),
|
||||
]),
|
||||
Event::Assert(Assert::IsPresent("GIROPAY Testbank 1")),
|
||||
Event::Trigger(Trigger::Click(By::Css("button[name='claimCheckoutButton']"))),
|
||||
Event::Assert(Assert::IsPresent("sandbox.paydirekt")),
|
||||
Event::Trigger(Trigger::Click(By::Id("submitButton"))),
|
||||
Event::Trigger(Trigger::Sleep(5)),
|
||||
Event::Trigger(Trigger::SwitchTab(Position::Next)),
|
||||
Event::Assert(Assert::IsPresent("Sicher bezahlt!")),
|
||||
Event::Assert(Assert::IsPresent("Your transaction")) // Transaction succeeds sometimes and pending sometimes
|
||||
]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_make_nuvei_ideal_payment(c: WebDriver) -> Result<(), WebDriverError> {
|
||||
let conn = NuveiSeleniumTest {};
|
||||
conn.make_redirection_payment(c, vec![
|
||||
Event::Trigger(Trigger::Goto("https://hs-payment-tests.w3spaces.com?pay-mode=bank-redirect&amount=10.00&country=NL¤cy=EUR&paymentmethod=ideal&processingbank=ing")),
|
||||
Event::Trigger(Trigger::Click(By::Id("bank-redirect-btn"))),
|
||||
Event::Assert(Assert::IsPresent("Your account will be debited:")),
|
||||
Event::Trigger(Trigger::SelectOption(By::Id("ctl00_ctl00_mainContent_ServiceContent_ddlBanks"), "ING Simulator")),
|
||||
Event::Trigger(Trigger::Click(By::Id("ctl00_ctl00_mainContent_btnConfirm"))),
|
||||
Event::Assert(Assert::IsPresent("IDEALFORTIS")),
|
||||
Event::Trigger(Trigger::Sleep(5)),
|
||||
Event::Trigger(Trigger::Click(By::Id("ctl00_mainContent_btnGo"))),
|
||||
Event::Assert(Assert::IsPresent("Your transaction")),// Transaction succeeds sometimes and pending sometimes
|
||||
]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_make_nuvei_sofort_payment(c: WebDriver) -> Result<(), WebDriverError> {
|
||||
let conn = NuveiSeleniumTest {};
|
||||
conn.make_redirection_payment(c, vec![
|
||||
Event::Trigger(Trigger::Goto("https://hs-payment-tests.w3spaces.com?pay-mode=bank-redirect&amount=10.00&country=DE¤cy=EUR&paymentmethod=sofort")),
|
||||
Event::Trigger(Trigger::Click(By::Id("bank-redirect-btn"))),
|
||||
Event::Assert(Assert::IsPresent("SOFORT")),
|
||||
Event::Trigger(Trigger::ChangeQueryParam("sender_holder", "John Doe")),
|
||||
Event::Trigger(Trigger::Click(By::Id("ctl00_mainContent_btnGo"))),
|
||||
Event::Assert(Assert::IsPresent("Your transaction")),// Transaction succeeds sometimes and pending sometimes
|
||||
]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_make_nuvei_eps_payment(c: WebDriver) -> Result<(), WebDriverError> {
|
||||
let conn = NuveiSeleniumTest {};
|
||||
conn.make_redirection_payment(c, vec![
|
||||
Event::Trigger(Trigger::Goto("https://hs-payment-tests.w3spaces.com?pay-mode=bank-redirect&amount=10.00&country=AT¤cy=EUR&paymentmethod=eps&processingbank=ing")),
|
||||
Event::Trigger(Trigger::Click(By::Id("bank-redirect-btn"))),
|
||||
Event::Assert(Assert::IsPresent("You are about to make a payment using the EPS service.")),
|
||||
Event::Trigger(Trigger::SendKeys(By::Id("ctl00_ctl00_mainContent_ServiceContent_txtCustomerName"), "John Doe")),
|
||||
Event::Trigger(Trigger::Click(By::Id("ctl00_ctl00_mainContent_btnConfirm"))),
|
||||
Event::Assert(Assert::IsPresent("Simulator")),
|
||||
Event::Trigger(Trigger::SelectOption(By::Css("select[name='result']"), "Succeeded")),
|
||||
Event::Trigger(Trigger::Click(By::Id("submitbutton"))),
|
||||
Event::Assert(Assert::IsPresent("Your transaction")),// Transaction succeeds sometimes and pending sometimes
|
||||
]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn should_make_nuvei_3ds_payment_test() {
|
||||
tester!(should_make_nuvei_3ds_payment, "firefox");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn should_make_nuvei_3ds_mandate_payment_test() {
|
||||
tester!(should_make_nuvei_3ds_mandate_payment, "firefox");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn should_make_nuvei_gpay_payment_test() {
|
||||
tester!(should_make_nuvei_gpay_payment, "firefox");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn should_make_nuvei_pypl_payment_test() {
|
||||
tester!(should_make_nuvei_pypl_payment, "firefox");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn should_make_nuvei_giropay_payment_test() {
|
||||
tester!(should_make_nuvei_giropay_payment, "firefox");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn should_make_nuvei_ideal_payment_test() {
|
||||
tester!(should_make_nuvei_ideal_payment, "firefox");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn should_make_nuvei_sofort_payment_test() {
|
||||
tester!(should_make_nuvei_sofort_payment, "firefox");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn should_make_nuvei_eps_payment_test() {
|
||||
tester!(should_make_nuvei_eps_payment, "firefox");
|
||||
}
|
||||
469
crates/router/tests/connectors/selenium.rs
Normal file
469
crates/router/tests/connectors/selenium.rs
Normal file
@ -0,0 +1,469 @@
|
||||
use std::{collections::HashMap, env, path::MAIN_SEPARATOR, time::Duration};
|
||||
|
||||
use actix_web::cookie::SameSite;
|
||||
use async_trait::async_trait;
|
||||
use futures::Future;
|
||||
use thirtyfour::{components::SelectElement, prelude::*, WebDriver};
|
||||
|
||||
pub enum Event<'a> {
|
||||
RunIf(Assert<'a>, Vec<Event<'a>>),
|
||||
EitherOr(Assert<'a>, Vec<Event<'a>>, Vec<Event<'a>>),
|
||||
Assert(Assert<'a>),
|
||||
Trigger(Trigger<'a>),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum Trigger<'a> {
|
||||
Goto(&'a str),
|
||||
Click(By),
|
||||
ClickNth(By, usize),
|
||||
SelectOption(By, &'a str),
|
||||
ChangeQueryParam(&'a str, &'a str),
|
||||
SwitchTab(Position),
|
||||
SwitchFrame(By),
|
||||
Find(By),
|
||||
Query(By),
|
||||
SendKeys(By, &'a str),
|
||||
Sleep(u64),
|
||||
}
|
||||
|
||||
pub enum Position {
|
||||
Prev,
|
||||
Next,
|
||||
}
|
||||
pub enum Selector {
|
||||
Title,
|
||||
QueryParamStr,
|
||||
}
|
||||
|
||||
pub enum Assert<'a> {
|
||||
Eq(Selector, &'a str),
|
||||
Contains(Selector, &'a str),
|
||||
IsPresent(&'a str),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait SeleniumTest {
|
||||
async fn complete_actions(
|
||||
&self,
|
||||
driver: &WebDriver,
|
||||
actions: Vec<Event<'_>>,
|
||||
) -> Result<(), WebDriverError> {
|
||||
for action in actions {
|
||||
match action {
|
||||
Event::Assert(assert) => match assert {
|
||||
Assert::Contains(selector, text) => match selector {
|
||||
Selector::QueryParamStr => {
|
||||
let url = driver.current_url().await?;
|
||||
assert!(url.query().unwrap().contains(text))
|
||||
}
|
||||
_ => assert!(driver.title().await?.contains(text)),
|
||||
},
|
||||
Assert::Eq(_selector, text) => assert_eq!(driver.title().await?, text),
|
||||
Assert::IsPresent(text) => {
|
||||
assert!(is_text_present(driver, text).await?)
|
||||
}
|
||||
},
|
||||
Event::RunIf(con_event, events) => match con_event {
|
||||
Assert::Contains(selector, text) => match selector {
|
||||
Selector::QueryParamStr => {
|
||||
let url = driver.current_url().await?;
|
||||
if url.query().unwrap().contains(text) {
|
||||
self.complete_actions(driver, events).await?;
|
||||
}
|
||||
}
|
||||
_ => assert!(driver.title().await?.contains(text)),
|
||||
},
|
||||
Assert::Eq(_selector, text) => {
|
||||
if text == driver.title().await? {
|
||||
self.complete_actions(driver, events).await?;
|
||||
}
|
||||
}
|
||||
Assert::IsPresent(text) => {
|
||||
if is_text_present(driver, text).await.is_ok() {
|
||||
self.complete_actions(driver, events).await?;
|
||||
}
|
||||
}
|
||||
},
|
||||
Event::EitherOr(con_event, success, failure) => match con_event {
|
||||
Assert::Contains(selector, text) => match selector {
|
||||
Selector::QueryParamStr => {
|
||||
let url = driver.current_url().await?;
|
||||
self.complete_actions(
|
||||
driver,
|
||||
if url.query().unwrap().contains(text) {
|
||||
success
|
||||
} else {
|
||||
failure
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
_ => assert!(driver.title().await?.contains(text)),
|
||||
},
|
||||
Assert::Eq(_selector, text) => {
|
||||
self.complete_actions(
|
||||
driver,
|
||||
if text == driver.title().await? {
|
||||
success
|
||||
} else {
|
||||
failure
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Assert::IsPresent(text) => {
|
||||
self.complete_actions(
|
||||
driver,
|
||||
if is_text_present(driver, text).await.is_ok() {
|
||||
success
|
||||
} else {
|
||||
failure
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
Event::Trigger(trigger) => match trigger {
|
||||
Trigger::Goto(url) => {
|
||||
driver.goto(url).await?;
|
||||
let hs_base_url =
|
||||
env::var("HS_BASE_URL").unwrap_or("http://localhost:8080".to_string()); //Issue: #924
|
||||
let hs_api_key =
|
||||
env::var("HS_API_KEY").expect("Hyperswitch user API key not present"); //Issue: #924
|
||||
driver
|
||||
.add_cookie(new_cookie("hs_base_url", hs_base_url).clone())
|
||||
.await?;
|
||||
driver
|
||||
.add_cookie(new_cookie("hs_api_key", hs_api_key).clone())
|
||||
.await?;
|
||||
}
|
||||
Trigger::Click(by) => {
|
||||
let ele = driver.query(by).first().await?;
|
||||
ele.wait_until().displayed().await?;
|
||||
ele.wait_until().clickable().await?;
|
||||
ele.click().await?;
|
||||
}
|
||||
Trigger::ClickNth(by, n) => {
|
||||
let ele = driver.query(by).all().await?.into_iter().nth(n).unwrap();
|
||||
ele.wait_until().displayed().await?;
|
||||
ele.wait_until().clickable().await?;
|
||||
ele.click().await?;
|
||||
}
|
||||
Trigger::Find(by) => {
|
||||
driver.find(by).await?;
|
||||
}
|
||||
Trigger::Query(by) => {
|
||||
driver.query(by).first().await?;
|
||||
}
|
||||
Trigger::SendKeys(by, input) => {
|
||||
let ele = driver.query(by).first().await?;
|
||||
ele.wait_until().displayed().await?;
|
||||
ele.send_keys(&input).await?;
|
||||
}
|
||||
Trigger::SelectOption(by, input) => {
|
||||
let ele = driver.query(by).first().await?;
|
||||
let select_element = SelectElement::new(&ele).await?;
|
||||
select_element.select_by_partial_text(input).await?;
|
||||
}
|
||||
Trigger::ChangeQueryParam(param, value) => {
|
||||
let mut url = driver.current_url().await?;
|
||||
let mut hash_query: HashMap<String, String> =
|
||||
url.query_pairs().into_owned().collect();
|
||||
hash_query.insert(param.to_string(), value.to_string());
|
||||
let url_str = serde_urlencoded::to_string(hash_query)
|
||||
.expect("Query Param update failed");
|
||||
url.set_query(Some(&url_str));
|
||||
driver.goto(url.as_str()).await?;
|
||||
}
|
||||
Trigger::Sleep(seconds) => {
|
||||
tokio::time::sleep(Duration::from_secs(seconds)).await;
|
||||
}
|
||||
Trigger::SwitchTab(position) => match position {
|
||||
Position::Next => {
|
||||
let windows = driver.windows().await?;
|
||||
if let Some(window) = windows.iter().rev().next() {
|
||||
driver.switch_to_window(window.to_owned()).await?;
|
||||
}
|
||||
}
|
||||
Position::Prev => {
|
||||
let windows = driver.windows().await?;
|
||||
if let Some(window) = windows.into_iter().next() {
|
||||
driver.switch_to_window(window.to_owned()).await?;
|
||||
}
|
||||
}
|
||||
},
|
||||
Trigger::SwitchFrame(by) => {
|
||||
let iframe = driver.query(by).first().await?;
|
||||
iframe.wait_until().displayed().await?;
|
||||
iframe.clone().enter_frame().await?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_payment<F, Fut>(&self, _f: F) -> Result<(), WebDriverError>
|
||||
where
|
||||
F: FnOnce(WebDriver) -> Fut + Send,
|
||||
Fut: Future<Output = Result<(), WebDriverError>> + Send,
|
||||
{
|
||||
let _browser = env::var("HS_TEST_BROWSER").unwrap_or("chrome".to_string()); //Issue: #924
|
||||
Ok(())
|
||||
}
|
||||
async fn make_redirection_payment(
|
||||
&self,
|
||||
c: WebDriver,
|
||||
actions: Vec<Event<'_>>,
|
||||
) -> Result<(), WebDriverError> {
|
||||
self.complete_actions(&c, actions).await
|
||||
}
|
||||
async fn make_gpay_payment(
|
||||
&self,
|
||||
c: WebDriver,
|
||||
url: &str,
|
||||
actions: Vec<Event<'_>>,
|
||||
) -> Result<(), WebDriverError> {
|
||||
let (email, pass) = (
|
||||
&get_env("GMAIL_EMAIL").clone(),
|
||||
&get_env("GMAIL_PASS").clone(),
|
||||
);
|
||||
let default_actions = vec![
|
||||
Event::Trigger(Trigger::Goto(url)),
|
||||
Event::Trigger(Trigger::Click(By::Css("#gpay-btn button"))),
|
||||
Event::Trigger(Trigger::SwitchTab(Position::Next)),
|
||||
Event::RunIf(
|
||||
Assert::IsPresent("Sign in"),
|
||||
vec![
|
||||
Event::Trigger(Trigger::SendKeys(By::Id("identifierId"), email)),
|
||||
Event::Trigger(Trigger::ClickNth(By::Tag("button"), 2)),
|
||||
Event::EitherOr(
|
||||
Assert::IsPresent("Welcome"),
|
||||
vec![
|
||||
Event::Trigger(Trigger::SendKeys(By::Name("Passwd"), pass)),
|
||||
Event::Trigger(Trigger::Sleep(2)),
|
||||
Event::Trigger(Trigger::Click(By::Id("passwordNext"))),
|
||||
],
|
||||
vec![
|
||||
Event::Trigger(Trigger::SendKeys(By::Id("identifierId"), email)),
|
||||
Event::Trigger(Trigger::ClickNth(By::Tag("button"), 2)),
|
||||
Event::Trigger(Trigger::SendKeys(By::Name("Passwd"), pass)),
|
||||
Event::Trigger(Trigger::Sleep(2)),
|
||||
Event::Trigger(Trigger::Click(By::Id("passwordNext"))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Event::Trigger(Trigger::Query(By::ClassName(
|
||||
"bootstrapperIframeContainerElement",
|
||||
))),
|
||||
Event::Trigger(Trigger::SwitchFrame(By::Id("sM432dIframe"))),
|
||||
Event::Assert(Assert::IsPresent("Gpay Tester")),
|
||||
Event::Trigger(Trigger::Click(By::ClassName("jfk-button-action"))),
|
||||
Event::Trigger(Trigger::SwitchTab(Position::Prev)),
|
||||
];
|
||||
self.complete_actions(&c, default_actions).await?;
|
||||
self.complete_actions(&c, actions).await
|
||||
}
|
||||
async fn make_paypal_payment(
|
||||
&self,
|
||||
c: WebDriver,
|
||||
url: &str,
|
||||
actions: Vec<Event<'_>>,
|
||||
) -> Result<(), WebDriverError> {
|
||||
self.complete_actions(
|
||||
&c,
|
||||
vec![
|
||||
Event::Trigger(Trigger::Goto(url)),
|
||||
Event::Trigger(Trigger::Click(By::Id("pypl-redirect-btn"))),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
let (email, pass) = (
|
||||
&get_env("PYPL_EMAIL").clone(),
|
||||
&get_env("PYPL_PASS").clone(),
|
||||
);
|
||||
let mut pypl_actions = vec![
|
||||
Event::EitherOr(
|
||||
Assert::IsPresent("Password"),
|
||||
vec![
|
||||
Event::Trigger(Trigger::SendKeys(By::Id("password"), pass)),
|
||||
Event::Trigger(Trigger::Click(By::Id("btnLogin"))),
|
||||
],
|
||||
vec![
|
||||
Event::Trigger(Trigger::SendKeys(By::Id("email"), email)),
|
||||
Event::Trigger(Trigger::Click(By::Id("btnNext"))),
|
||||
Event::Trigger(Trigger::SendKeys(By::Id("password"), pass)),
|
||||
Event::Trigger(Trigger::Click(By::Id("btnLogin"))),
|
||||
],
|
||||
),
|
||||
Event::Trigger(Trigger::Click(By::Id("payment-submit-btn"))),
|
||||
];
|
||||
pypl_actions.extend(actions);
|
||||
self.complete_actions(&c, pypl_actions).await
|
||||
}
|
||||
}
|
||||
async fn is_text_present(driver: &WebDriver, key: &str) -> WebDriverResult<bool> {
|
||||
let mut xpath = "//*[contains(text(),'".to_owned();
|
||||
xpath.push_str(key);
|
||||
xpath.push_str("')]");
|
||||
let result = driver.query(By::XPath(&xpath)).first().await?;
|
||||
result.is_present().await
|
||||
}
|
||||
fn new_cookie(name: &str, value: String) -> Cookie<'_> {
|
||||
let mut base_url_cookie = Cookie::new(name, value);
|
||||
base_url_cookie.set_same_site(Some(SameSite::Lax));
|
||||
base_url_cookie.set_domain("hs-payment-tests.w3spaces.com");
|
||||
base_url_cookie.set_path("/");
|
||||
base_url_cookie
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! tester_inner {
|
||||
($execute:ident, $webdriver:expr) => {{
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
let driver = $webdriver;
|
||||
|
||||
// we'll need the session_id from the thread
|
||||
// NOTE: even if it panics, so can't just return it
|
||||
let session_id = Arc::new(Mutex::new(None));
|
||||
|
||||
// run test in its own thread to catch panics
|
||||
let sid = session_id.clone();
|
||||
let res = thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let driver = runtime
|
||||
.block_on(driver)
|
||||
.expect("failed to construct test WebDriver");
|
||||
*sid.lock().unwrap() = runtime.block_on(driver.session_id()).ok();
|
||||
// make sure we close, even if an assertion fails
|
||||
let client = driver.clone();
|
||||
let x = runtime.block_on(async move {
|
||||
let r = tokio::spawn($execute(driver)).await;
|
||||
let _ = client.quit().await;
|
||||
r
|
||||
});
|
||||
drop(runtime);
|
||||
x.expect("test panicked")
|
||||
})
|
||||
.join();
|
||||
let success = handle_test_error(res);
|
||||
assert!(success);
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! tester {
|
||||
($f:ident, $endpoint:expr) => {{
|
||||
use $crate::tester_inner;
|
||||
|
||||
let url = make_url($endpoint);
|
||||
let caps = make_capabilities($endpoint);
|
||||
tester_inner!($f, WebDriver::new(url, caps));
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn make_capabilities(s: &str) -> Capabilities {
|
||||
match s {
|
||||
"firefox" => {
|
||||
let mut caps = DesiredCapabilities::firefox();
|
||||
let profile_path = &format!("-profile={}", get_firefox_profile_path().unwrap());
|
||||
caps.add_firefox_arg(profile_path).unwrap();
|
||||
// let mut prefs = FirefoxPreferences::new();
|
||||
// prefs.set("-browser.link.open_newwindow", 3).unwrap();
|
||||
// caps.set_preferences(prefs).unwrap();
|
||||
caps.into()
|
||||
}
|
||||
"chrome" => {
|
||||
let mut caps = DesiredCapabilities::chrome();
|
||||
let profile_path = &format!("user-data-dir={}", get_chrome_profile_path().unwrap());
|
||||
caps.add_chrome_arg(profile_path).unwrap();
|
||||
// caps.set_headless().unwrap();
|
||||
// caps.set_no_sandbox().unwrap();
|
||||
// caps.set_disable_gpu().unwrap();
|
||||
// caps.set_disable_dev_shm_usage().unwrap();
|
||||
caps.into()
|
||||
}
|
||||
&_ => DesiredCapabilities::safari().into(),
|
||||
}
|
||||
}
|
||||
fn get_chrome_profile_path() -> Result<String, WebDriverError> {
|
||||
env::var("CHROME_PROFILE_PATH").map_or_else(
|
||||
//Issue: #924
|
||||
|_| -> Result<String, WebDriverError> {
|
||||
let exe = env::current_exe()?;
|
||||
let dir = exe.parent().expect("Executable must be in some directory");
|
||||
let mut base_path = dir
|
||||
.to_str()
|
||||
.map(|str| {
|
||||
let mut fp = str.split(MAIN_SEPARATOR).collect::<Vec<_>>();
|
||||
fp.truncate(3);
|
||||
fp.join(&MAIN_SEPARATOR.to_string())
|
||||
})
|
||||
.unwrap();
|
||||
base_path.push_str(r#"/Library/Application\ Support/Google/Chrome/Default"#);
|
||||
Ok(base_path)
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
fn get_firefox_profile_path() -> Result<String, WebDriverError> {
|
||||
env::var("FIREFOX_PROFILE_PATH").map_or_else(
|
||||
//Issue: #924
|
||||
|_| -> Result<String, WebDriverError> {
|
||||
let exe = env::current_exe()?;
|
||||
let dir = exe.parent().expect("Executable must be in some directory");
|
||||
let mut base_path = dir
|
||||
.to_str()
|
||||
.map(|str| {
|
||||
let mut fp = str.split(MAIN_SEPARATOR).collect::<Vec<_>>();
|
||||
fp.truncate(3);
|
||||
fp.join(&MAIN_SEPARATOR.to_string())
|
||||
})
|
||||
.unwrap();
|
||||
base_path.push_str(r#"/Library/Application Support/Firefox/Profiles/hs-test"#);
|
||||
Ok(base_path)
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn make_url(s: &str) -> &'static str {
|
||||
match s {
|
||||
"firefox" => "http://localhost:4444",
|
||||
"chrome" => "http://localhost:9515",
|
||||
&_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_test_error(
|
||||
res: Result<Result<(), WebDriverError>, Box<dyn std::any::Any + Send>>,
|
||||
) -> bool {
|
||||
match res {
|
||||
Ok(Ok(_)) => true,
|
||||
Ok(Err(e)) => {
|
||||
eprintln!("test future failed to resolve: {:?}", e);
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(e) = e.downcast_ref::<WebDriverError>() {
|
||||
eprintln!("test future panicked: {:?}", e);
|
||||
} else {
|
||||
eprintln!("test future panicked; an assertion probably failed");
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_env(name: &str) -> String {
|
||||
env::var(name).unwrap_or_else(|_| panic!("{name} not present")) //Issue: #924
|
||||
}
|
||||
@ -400,6 +400,7 @@ pub trait ConnectorActions: Connector {
|
||||
Ok(types::PaymentsResponseData::SessionTokenResponse { .. }) => None,
|
||||
Ok(types::PaymentsResponseData::TokenizationResponse { .. }) => None,
|
||||
Ok(types::PaymentsResponseData::TransactionUnresolvedResponse { .. }) => None,
|
||||
Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. }) => None,
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
@ -581,6 +582,7 @@ pub fn get_connector_transaction_id(
|
||||
Ok(types::PaymentsResponseData::SessionTokenResponse { .. }) => None,
|
||||
Ok(types::PaymentsResponseData::TokenizationResponse { .. }) => None,
|
||||
Ok(types::PaymentsResponseData::TransactionUnresolvedResponse { .. }) => None,
|
||||
Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. }) => None,
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user