mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
feat(connector): [NMI] Implement 3DS for Cards (#3143)
Co-authored-by: Arjun Karthik <m.arjunkarthik@gmail.com> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com>
This commit is contained in:
@ -187,6 +187,90 @@ impl
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentsPreProcessing for Nmi {}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::PreProcessing,
|
||||
types::PaymentsPreProcessingData,
|
||||
types::PaymentsResponseData,
|
||||
> for Nmi
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsPreProcessingRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsPreProcessingRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}api/transact.php", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsPreProcessingRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let connector_req = nmi::NmiVaultRequest::try_from(req)?;
|
||||
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsPreProcessingRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let req = Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.attach_default_headers()
|
||||
.headers(types::PaymentsPreProcessingType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.url(&types::PaymentsPreProcessingType::get_url(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.set_body(types::PaymentsPreProcessingType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
);
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsPreProcessingRouterData,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
|
||||
let response: nmi::NmiVaultResponse = serde_urlencoded::from_bytes(&res.response)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||
for Nmi
|
||||
{
|
||||
@ -265,6 +349,91 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentsCompleteAuthorize for Nmi {}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::CompleteAuthorize,
|
||||
types::CompleteAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
> for Nmi
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}api/transact.php", self.base_url(connectors)))
|
||||
}
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let connector_router_data = nmi::NmiRouterData::try_from((
|
||||
&self.get_currency_unit(),
|
||||
req.request.currency,
|
||||
req.request.amount,
|
||||
req,
|
||||
))?;
|
||||
let connector_req = nmi::NmiCompleteRequest::try_from(&connector_router_data)?;
|
||||
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
|
||||
}
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsCompleteAuthorizeType::get_url(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::PaymentsCompleteAuthorizeType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.set_body(types::PaymentsCompleteAuthorizeType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsCompleteAuthorizeRouterData,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
|
||||
let response: nmi::NmiCompleteResponse = serde_urlencoded::from_bytes(&res.response)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Nmi
|
||||
{
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
use cards::CardNumber;
|
||||
use common_utils::ext_traits::XmlExt;
|
||||
use common_utils::{errors::CustomResult, ext_traits::XmlExt};
|
||||
use error_stack::{IntoReport, Report, ResultExt};
|
||||
use masking::Secret;
|
||||
use masking::{ExposeInterface, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils::{self, PaymentsAuthorizeRequestData},
|
||||
connector::utils::{self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData},
|
||||
core::errors,
|
||||
services,
|
||||
types::{self, api, storage::enums, transformers::ForeignFrom, ConnectorAuthType},
|
||||
};
|
||||
|
||||
@ -25,17 +26,22 @@ pub enum TransactionType {
|
||||
|
||||
pub struct NmiAuthType {
|
||||
pub(super) api_key: Secret<String>,
|
||||
pub(super) public_key: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
impl TryFrom<&ConnectorAuthType> for NmiAuthType {
|
||||
type Error = Error;
|
||||
fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
|
||||
if let types::ConnectorAuthType::HeaderKey { api_key } = auth_type {
|
||||
Ok(Self {
|
||||
match auth_type {
|
||||
types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
|
||||
api_key: api_key.to_owned(),
|
||||
})
|
||||
} else {
|
||||
Err(errors::ConnectorError::FailedToObtainAuthType.into())
|
||||
public_key: None,
|
||||
}),
|
||||
types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
|
||||
api_key: api_key.to_owned(),
|
||||
public_key: Some(key1.to_owned()),
|
||||
}),
|
||||
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,6 +77,291 @@ impl<T>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NmiVaultRequest {
|
||||
security_key: Secret<String>,
|
||||
ccnumber: CardNumber,
|
||||
ccexp: Secret<String>,
|
||||
customer_vault: CustomerAction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CustomerAction {
|
||||
AddCustomer,
|
||||
UpdateCustomer,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsPreProcessingRouterData> for NmiVaultRequest {
|
||||
type Error = Error;
|
||||
fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> {
|
||||
let auth_type: NmiAuthType = (&item.connector_auth_type).try_into()?;
|
||||
let (ccnumber, ccexp) = get_card_details(item.request.payment_method_data.clone())?;
|
||||
|
||||
Ok(Self {
|
||||
security_key: auth_type.api_key,
|
||||
ccnumber,
|
||||
ccexp,
|
||||
customer_vault: CustomerAction::AddCustomer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_card_details(
|
||||
payment_method_data: Option<api::PaymentMethodData>,
|
||||
) -> CustomResult<(CardNumber, Secret<String>), errors::ConnectorError> {
|
||||
match payment_method_data {
|
||||
Some(api::PaymentMethodData::Card(ref card_details)) => Ok((
|
||||
card_details.card_number.clone(),
|
||||
utils::CardData::get_card_expiry_month_year_2_digit_with_delimiter(
|
||||
card_details,
|
||||
"".to_string(),
|
||||
),
|
||||
)),
|
||||
_ => Err(errors::ConnectorError::NotImplemented(
|
||||
utils::get_unimplemented_payment_method_error_message("Nmi"),
|
||||
))
|
||||
.into_report(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NmiVaultResponse {
|
||||
pub response: Response,
|
||||
pub responsetext: String,
|
||||
pub customer_vault_id: String,
|
||||
pub response_code: String,
|
||||
}
|
||||
|
||||
impl
|
||||
TryFrom<
|
||||
types::ResponseRouterData<
|
||||
api::PreProcessing,
|
||||
NmiVaultResponse,
|
||||
types::PaymentsPreProcessingData,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
> for types::PaymentsPreProcessingRouterData
|
||||
{
|
||||
type Error = Error;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<
|
||||
api::PreProcessing,
|
||||
NmiVaultResponse,
|
||||
types::PaymentsPreProcessingData,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let auth_type: NmiAuthType = (&item.data.connector_auth_type).try_into()?;
|
||||
let amount_data =
|
||||
item.data
|
||||
.request
|
||||
.amount
|
||||
.ok_or(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "amount",
|
||||
})?;
|
||||
let currency_data =
|
||||
item.data
|
||||
.request
|
||||
.currency
|
||||
.ok_or(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "currency",
|
||||
})?;
|
||||
let (response, status) = match item.response.response {
|
||||
Response::Approved => (
|
||||
Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::NoResponseId,
|
||||
redirection_data: Some(services::RedirectForm::Nmi {
|
||||
amount: utils::to_currency_base_unit_asf64(
|
||||
amount_data,
|
||||
currency_data.to_owned(),
|
||||
)?
|
||||
.to_string(),
|
||||
currency: currency_data,
|
||||
customer_vault_id: item.response.customer_vault_id,
|
||||
public_key: auth_type.public_key.ok_or(
|
||||
errors::ConnectorError::InvalidConnectorConfig {
|
||||
config: "public_key",
|
||||
},
|
||||
)?,
|
||||
}),
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
incremental_authorization_allowed: None,
|
||||
}),
|
||||
enums::AttemptStatus::AuthenticationPending,
|
||||
),
|
||||
Response::Declined | Response::Error => (
|
||||
Err(types::ErrorResponse {
|
||||
code: item.response.response_code,
|
||||
message: item.response.responsetext,
|
||||
reason: None,
|
||||
status_code: item.http_code,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
}),
|
||||
enums::AttemptStatus::Failure,
|
||||
),
|
||||
};
|
||||
Ok(Self {
|
||||
status,
|
||||
response,
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NmiCompleteRequest {
|
||||
#[serde(rename = "type")]
|
||||
transaction_type: TransactionType,
|
||||
security_key: Secret<String>,
|
||||
cardholder_auth: CardHolderAuthType,
|
||||
cavv: String,
|
||||
xid: String,
|
||||
three_ds_version: ThreeDsVersion,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum CardHolderAuthType {
|
||||
Verified,
|
||||
Attempted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum ThreeDsVersion {
|
||||
#[serde(rename = "2.0.0")]
|
||||
VersionTwo,
|
||||
#[serde(rename = "2.2.0")]
|
||||
VersionTwoPointTwo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NmiRedirectResponseData {
|
||||
cavv: String,
|
||||
xid: String,
|
||||
card_holder_auth: CardHolderAuthType,
|
||||
three_ds_version: ThreeDsVersion,
|
||||
}
|
||||
|
||||
impl TryFrom<&NmiRouterData<&types::PaymentsCompleteAuthorizeRouterData>> for NmiCompleteRequest {
|
||||
type Error = Error;
|
||||
fn try_from(
|
||||
item: &NmiRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let transaction_type = match item.router_data.request.is_auto_capture()? {
|
||||
true => TransactionType::Sale,
|
||||
false => TransactionType::Auth,
|
||||
};
|
||||
let auth_type: NmiAuthType = (&item.router_data.connector_auth_type).try_into()?;
|
||||
let payload_data = item
|
||||
.router_data
|
||||
.request
|
||||
.get_redirect_response_payload()?
|
||||
.expose();
|
||||
|
||||
let three_ds_data: NmiRedirectResponseData = serde_json::from_value(payload_data)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::MissingConnectorRedirectionPayload {
|
||||
field_name: "three_ds_data",
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
transaction_type,
|
||||
security_key: auth_type.api_key,
|
||||
cardholder_auth: three_ds_data.card_holder_auth,
|
||||
cavv: three_ds_data.cavv,
|
||||
xid: three_ds_data.xid,
|
||||
three_ds_version: three_ds_data.three_ds_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NmiCompleteResponse {
|
||||
pub response: Response,
|
||||
pub responsetext: String,
|
||||
pub authcode: Option<String>,
|
||||
pub transactionid: String,
|
||||
pub avsresponse: Option<String>,
|
||||
pub cvvresponse: Option<String>,
|
||||
pub orderid: String,
|
||||
pub response_code: String,
|
||||
}
|
||||
|
||||
impl
|
||||
TryFrom<
|
||||
types::ResponseRouterData<
|
||||
api::CompleteAuthorize,
|
||||
NmiCompleteResponse,
|
||||
types::CompleteAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
> for types::PaymentsCompleteAuthorizeRouterData
|
||||
{
|
||||
type Error = Error;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<
|
||||
api::CompleteAuthorize,
|
||||
NmiCompleteResponse,
|
||||
types::CompleteAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let (response, status) = match item.response.response {
|
||||
Response::Approved => (
|
||||
Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
item.response.transactionid,
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
incremental_authorization_allowed: None,
|
||||
}),
|
||||
if let Some(diesel_models::enums::CaptureMethod::Automatic) =
|
||||
item.data.request.capture_method
|
||||
{
|
||||
enums::AttemptStatus::CaptureInitiated
|
||||
} else {
|
||||
enums::AttemptStatus::Authorizing
|
||||
},
|
||||
),
|
||||
Response::Declined | Response::Error => (
|
||||
Err(types::ErrorResponse::foreign_from((
|
||||
item.response,
|
||||
item.http_code,
|
||||
))),
|
||||
enums::AttemptStatus::Failure,
|
||||
),
|
||||
};
|
||||
Ok(Self {
|
||||
status,
|
||||
response,
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignFrom<(NmiCompleteResponse, u16)> for types::ErrorResponse {
|
||||
fn foreign_from((response, http_code): (NmiCompleteResponse, u16)) -> Self {
|
||||
Self {
|
||||
code: response.response_code,
|
||||
message: response.responsetext,
|
||||
reason: None,
|
||||
status_code: http_code,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NmiPaymentsRequest {
|
||||
#[serde(rename = "type")]
|
||||
|
||||
@ -1479,6 +1479,13 @@ where
|
||||
let is_error_in_response = router_data.response.is_err();
|
||||
// If is_error_in_response is true, should_continue_payment should be false, we should throw the error
|
||||
(router_data, !is_error_in_response)
|
||||
} else if connector.connector_name == router_types::Connector::Nmi
|
||||
&& !matches!(format!("{operation:?}").as_str(), "CompleteAuthorize")
|
||||
&& router_data.auth_type == storage_enums::AuthenticationType::ThreeDs
|
||||
{
|
||||
router_data = router_data.preprocessing_steps(state, connector).await?;
|
||||
|
||||
(router_data, false)
|
||||
} else {
|
||||
(router_data, should_continue_payment)
|
||||
}
|
||||
|
||||
@ -165,7 +165,6 @@ default_imp_for_complete_authorize!(
|
||||
connector::Klarna,
|
||||
connector::Multisafepay,
|
||||
connector::Nexinets,
|
||||
connector::Nmi,
|
||||
connector::Noon,
|
||||
connector::Opayo,
|
||||
connector::Opennode,
|
||||
@ -886,7 +885,6 @@ default_imp_for_pre_processing_steps!(
|
||||
connector::Mollie,
|
||||
connector::Multisafepay,
|
||||
connector::Nexinets,
|
||||
connector::Nmi,
|
||||
connector::Noon,
|
||||
connector::Nuvei,
|
||||
connector::Opayo,
|
||||
|
||||
@ -12,6 +12,7 @@ use std::{
|
||||
use actix_web::{body, web, FromRequest, HttpRequest, HttpResponse, Responder, ResponseError};
|
||||
use api_models::enums::CaptureMethod;
|
||||
pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient};
|
||||
use common_enums::Currency;
|
||||
pub use common_utils::request::{ContentType, Method, Request, RequestBuilder};
|
||||
use common_utils::{
|
||||
consts::X_HS_LATENCY,
|
||||
@ -19,7 +20,7 @@ use common_utils::{
|
||||
request::RequestContent,
|
||||
};
|
||||
use error_stack::{report, IntoReport, Report, ResultExt};
|
||||
use masking::PeekInterface;
|
||||
use masking::{PeekInterface, Secret};
|
||||
use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag};
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
@ -772,6 +773,12 @@ pub enum RedirectForm {
|
||||
card_token: String,
|
||||
bin: String,
|
||||
},
|
||||
Nmi {
|
||||
amount: String,
|
||||
currency: Currency,
|
||||
public_key: Secret<String>,
|
||||
customer_vault_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<(url::Url, Method)> for RedirectForm {
|
||||
@ -1495,6 +1502,79 @@ pub fn build_redirection_form(
|
||||
)))
|
||||
}}
|
||||
}
|
||||
RedirectForm::Nmi {
|
||||
amount,
|
||||
currency,
|
||||
public_key,
|
||||
customer_vault_id,
|
||||
} => {
|
||||
let public_key_val = public_key.peek();
|
||||
maud::html! {
|
||||
(maud::DOCTYPE)
|
||||
head {
|
||||
(PreEscaped(r#"<script src="https://secure.networkmerchants.com/js/v1/Gateway.js"></script>"#))
|
||||
}
|
||||
(PreEscaped(format!("<script>
|
||||
const gateway = Gateway.create('{public_key_val}');
|
||||
|
||||
// Initialize the ThreeDSService
|
||||
const threeDS = gateway.get3DSecure();
|
||||
|
||||
const options = {{
|
||||
customerVaultId: '{customer_vault_id}',
|
||||
currency: '{currency}',
|
||||
amount: '{amount}'
|
||||
}};
|
||||
|
||||
var responseForm = document.createElement('form');
|
||||
responseForm.action=window.location.pathname.replace(/payments\\/redirect\\/(\\w+)\\/(\\w+)\\/\\w+/, \"payments/$1/$2/redirect/complete/nmi\");
|
||||
responseForm.method='POST';
|
||||
|
||||
const threeDSsecureInterface = threeDS.createUI(options);
|
||||
threeDSsecureInterface.start('body');
|
||||
|
||||
threeDSsecureInterface.on('challenge', function(e) {{
|
||||
console.log('Challenged');
|
||||
}});
|
||||
|
||||
threeDSsecureInterface.on('complete', function(e) {{
|
||||
|
||||
var item1=document.createElement('input');
|
||||
item1.type='hidden';
|
||||
item1.name='cavv';
|
||||
item1.value=e.cavv;
|
||||
responseForm.appendChild(item1);
|
||||
|
||||
var item2=document.createElement('input');
|
||||
item2.type='hidden';
|
||||
item2.name='xid';
|
||||
item2.value=e.xid;
|
||||
responseForm.appendChild(item2);
|
||||
|
||||
var item3=document.createElement('input');
|
||||
item3.type='hidden';
|
||||
item3.name='cardHolderAuth';
|
||||
item3.value=e.cardHolderAuth;
|
||||
responseForm.appendChild(item3);
|
||||
|
||||
var item4=document.createElement('input');
|
||||
item4.type='hidden';
|
||||
item4.name='threeDsVersion';
|
||||
item4.value=e.threeDsVersion;
|
||||
responseForm.appendChild(item4);
|
||||
|
||||
document.body.appendChild(responseForm);
|
||||
responseForm.submit();
|
||||
}});
|
||||
|
||||
threeDSsecureInterface.on('failure', function(e) {{
|
||||
responseForm.submit();
|
||||
}});
|
||||
|
||||
</script>"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user