mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-04 05:59:48 +08:00
feat(connector): [Nuvei] Implement setup mandate flow for cards (#9012)
Co-authored-by: Vani Gupta <vani.gupta@juspay.in> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -844,7 +844,7 @@ pub fn get_avs_definition(code: &str) -> Option<&'static str> {
|
|||||||
"8" => Some("Cardholder name, address, and ZIP do not match"),
|
"8" => Some("Cardholder name, address, and ZIP do not match"),
|
||||||
_ => {
|
_ => {
|
||||||
router_env::logger::info!(
|
router_env::logger::info!(
|
||||||
"Celero avs response code ({:?}) is not mapped to any defination.",
|
"Celero avs response code ({:?}) is not mapped to any definition.",
|
||||||
code
|
code
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -129,6 +129,8 @@ impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, Pay
|
|||||||
// Not Implemented (R)
|
// Not Implemented (R)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Nuvei {}
|
||||||
|
|
||||||
impl api::MandateSetup for Nuvei {}
|
impl api::MandateSetup for Nuvei {}
|
||||||
impl api::PaymentVoid for Nuvei {}
|
impl api::PaymentVoid for Nuvei {}
|
||||||
impl api::PaymentSync for Nuvei {}
|
impl api::PaymentSync for Nuvei {}
|
||||||
@ -143,16 +145,87 @@ impl api::PaymentsCompleteAuthorize for Nuvei {}
|
|||||||
impl api::ConnectorAccessToken for Nuvei {}
|
impl api::ConnectorAccessToken for Nuvei {}
|
||||||
impl api::PaymentsPreProcessing for Nuvei {}
|
impl api::PaymentsPreProcessing for Nuvei {}
|
||||||
impl api::PaymentPostCaptureVoid for Nuvei {}
|
impl api::PaymentPostCaptureVoid for Nuvei {}
|
||||||
|
|
||||||
impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Nuvei {
|
impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Nuvei {
|
||||||
fn build_request(
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
|
||||||
|
connectors: &Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, masking::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,
|
&self,
|
||||||
_req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
|
_req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
|
||||||
|
connectors: &Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
Ok(format!(
|
||||||
|
"{}ppp/api/v1/payment.do",
|
||||||
|
ConnectorCommon::base_url(self, connectors)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
|
||||||
_connectors: &Connectors,
|
_connectors: &Connectors,
|
||||||
|
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||||
|
let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?;
|
||||||
|
Ok(RequestContent::Json(Box::new(connector_req)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
|
||||||
|
connectors: &Connectors,
|
||||||
) -> CustomResult<Option<Request>, errors::ConnectorError> {
|
) -> CustomResult<Option<Request>, errors::ConnectorError> {
|
||||||
Err(
|
Ok(Some(
|
||||||
errors::ConnectorError::NotImplemented("Setup Mandate flow for Nuvei".to_string())
|
RequestBuilder::new()
|
||||||
.into(),
|
.method(Method::Post)
|
||||||
)
|
.url(&types::SetupMandateType::get_url(self, req, connectors)?)
|
||||||
|
.attach_default_headers()
|
||||||
|
.headers(types::SetupMandateType::get_headers(self, req, connectors)?)
|
||||||
|
.set_body(types::SetupMandateType::get_request_body(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
|
||||||
|
event_builder: Option<&mut ConnectorEvent>,
|
||||||
|
res: Response,
|
||||||
|
) -> CustomResult<
|
||||||
|
RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
|
||||||
|
errors::ConnectorError,
|
||||||
|
> {
|
||||||
|
let response: nuvei::NuveiPaymentsResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("NuveiPaymentsResponse")
|
||||||
|
.switch()?;
|
||||||
|
event_builder.map(|i| i.set_response_body(&response));
|
||||||
|
router_env::logger::info!(connector_response=?response);
|
||||||
|
|
||||||
|
RouterData::try_from(ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_error_response(
|
||||||
|
&self,
|
||||||
|
res: Response,
|
||||||
|
event_builder: Option<&mut ConnectorEvent>,
|
||||||
|
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res, event_builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,8 +642,6 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Nuvei {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Nuvei {
|
impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Nuvei {
|
||||||
fn get_headers(
|
fn get_headers(
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
use common_enums::{enums, CaptureMethod, PaymentChannel};
|
use common_enums::{enums, CaptureMethod, FutureUsage, PaymentChannel};
|
||||||
use common_types::payments::{ApplePayPaymentData, GpayTokenizationData};
|
use common_types::payments::{ApplePayPaymentData, GpayTokenizationData};
|
||||||
use common_utils::{
|
use common_utils::{
|
||||||
crypto::{self, GenerateDigest},
|
crypto::{self, GenerateDigest},
|
||||||
date_time,
|
date_time,
|
||||||
ext_traits::{Encode, OptionExt},
|
ext_traits::Encode,
|
||||||
fp_utils,
|
fp_utils,
|
||||||
id_type::CustomerId,
|
id_type::CustomerId,
|
||||||
pii::{Email, IpAddress},
|
pii::{self, Email, IpAddress},
|
||||||
request::Method,
|
request::Method,
|
||||||
types::{MinorUnit, StringMajorUnit, StringMajorUnitForConnector},
|
types::{MinorUnit, StringMajorUnit, StringMajorUnitForConnector},
|
||||||
};
|
};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use hyperswitch_domain_models::{
|
use hyperswitch_domain_models::{
|
||||||
address::Address,
|
address::Address,
|
||||||
mandates::{MandateData, MandateDataType},
|
|
||||||
payment_method_data::{
|
payment_method_data::{
|
||||||
self, ApplePayWalletData, BankRedirectData, GooglePayWalletData, PayLaterData,
|
self, ApplePayWalletData, BankRedirectData, GooglePayWalletData, PayLaterData,
|
||||||
PaymentMethodData, WalletData,
|
PaymentMethodData, WalletData,
|
||||||
@ -24,11 +23,11 @@ use hyperswitch_domain_models::{
|
|||||||
},
|
},
|
||||||
router_flow_types::{
|
router_flow_types::{
|
||||||
refunds::{Execute, RSync},
|
refunds::{Execute, RSync},
|
||||||
Authorize, Capture, CompleteAuthorize, PSync, PostCaptureVoid, Void,
|
Authorize, Capture, CompleteAuthorize, PSync, PostCaptureVoid, SetupMandate, Void,
|
||||||
},
|
},
|
||||||
router_request_types::{
|
router_request_types::{
|
||||||
authentication::MessageExtensionAttribute, BrowserInformation, PaymentsAuthorizeData,
|
authentication::MessageExtensionAttribute, BrowserInformation, PaymentsAuthorizeData,
|
||||||
PaymentsPreProcessingData, ResponseId,
|
PaymentsPreProcessingData, ResponseId, SetupMandateRequestData,
|
||||||
},
|
},
|
||||||
router_response_types::{
|
router_response_types::{
|
||||||
MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData,
|
MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData,
|
||||||
@ -70,12 +69,11 @@ fn to_boolean(string: String) -> bool {
|
|||||||
// The dimensions of the challenge window for full screen.
|
// The dimensions of the challenge window for full screen.
|
||||||
const CHALLENGE_WINDOW_SIZE: &str = "05";
|
const CHALLENGE_WINDOW_SIZE: &str = "05";
|
||||||
// The challenge preference for the challenge flow.
|
// The challenge preference for the challenge flow.
|
||||||
const CHALLENGE_PREFERNCE: &str = "01";
|
const CHALLENGE_PREFERENCE: &str = "01";
|
||||||
|
|
||||||
trait NuveiAuthorizePreprocessingCommon {
|
trait NuveiAuthorizePreprocessingCommon {
|
||||||
fn get_browser_info(&self) -> Option<BrowserInformation>;
|
fn get_browser_info(&self) -> Option<BrowserInformation>;
|
||||||
fn get_related_transaction_id(&self) -> Option<String>;
|
fn get_related_transaction_id(&self) -> Option<String>;
|
||||||
fn get_setup_mandate_details(&self) -> Option<MandateData>;
|
|
||||||
fn get_complete_authorize_url(&self) -> Option<String>;
|
fn get_complete_authorize_url(&self) -> Option<String>;
|
||||||
fn get_is_moto(&self) -> Option<bool>;
|
fn get_is_moto(&self) -> Option<bool>;
|
||||||
fn get_connector_mandate_id(&self) -> Option<String>;
|
fn get_connector_mandate_id(&self) -> Option<String>;
|
||||||
@ -98,6 +96,92 @@ trait NuveiAuthorizePreprocessingCommon {
|
|||||||
fn get_order_tax_amount(
|
fn get_order_tax_amount(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Option<MinorUnit>, error_stack::Report<errors::ConnectorError>>;
|
) -> Result<Option<MinorUnit>, error_stack::Report<errors::ConnectorError>>;
|
||||||
|
fn is_customer_initiated_mandate_payment(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NuveiAuthorizePreprocessingCommon for SetupMandateRequestData {
|
||||||
|
fn get_browser_info(&self) -> Option<BrowserInformation> {
|
||||||
|
self.browser_info.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_related_transaction_id(&self) -> Option<String> {
|
||||||
|
self.related_transaction_id.clone()
|
||||||
|
}
|
||||||
|
fn get_is_moto(&self) -> Option<bool> {
|
||||||
|
match self.payment_channel {
|
||||||
|
Some(PaymentChannel::MailOrder) | Some(PaymentChannel::TelephoneOrder) => Some(true),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_customer_id_required(&self) -> Option<CustomerId> {
|
||||||
|
self.customer_id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_complete_authorize_url(&self) -> Option<String> {
|
||||||
|
self.complete_authorize_url.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_connector_mandate_id(&self) -> Option<String> {
|
||||||
|
self.mandate_id.as_ref().and_then(|mandate_ids| {
|
||||||
|
mandate_ids.mandate_reference_id.as_ref().and_then(
|
||||||
|
|mandate_ref_id| match mandate_ref_id {
|
||||||
|
api_models::payments::MandateReferenceId::ConnectorMandateId(id) => {
|
||||||
|
id.get_connector_mandate_id()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_return_url_required(
|
||||||
|
&self,
|
||||||
|
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
self.router_return_url
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(missing_field_err("return_url"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_capture_method(&self) -> Option<CaptureMethod> {
|
||||||
|
self.capture_method
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_currency_required(
|
||||||
|
&self,
|
||||||
|
) -> Result<enums::Currency, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
Ok(self.currency)
|
||||||
|
}
|
||||||
|
fn get_payment_method_data_required(
|
||||||
|
&self,
|
||||||
|
) -> Result<PaymentMethodData, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
Ok(self.payment_method_data.clone())
|
||||||
|
}
|
||||||
|
fn get_order_tax_amount(
|
||||||
|
&self,
|
||||||
|
) -> Result<Option<MinorUnit>, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_minor_amount_required(
|
||||||
|
&self,
|
||||||
|
) -> Result<MinorUnit, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
self.minor_amount
|
||||||
|
.ok_or_else(missing_field_err("minor_amount"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_is_partial_approval(&self) -> Option<PartialApprovalFlag> {
|
||||||
|
self.enable_partial_authorization
|
||||||
|
.map(PartialApprovalFlag::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
self.email.clone().ok_or_else(missing_field_err("email"))
|
||||||
|
}
|
||||||
|
fn is_customer_initiated_mandate_payment(&self) -> bool {
|
||||||
|
(self.customer_acceptance.is_some() || self.setup_mandate_details.is_some())
|
||||||
|
&& self.setup_future_usage == Some(FutureUsage::OffSession)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NuveiAuthorizePreprocessingCommon for PaymentsAuthorizeData {
|
impl NuveiAuthorizePreprocessingCommon for PaymentsAuthorizeData {
|
||||||
@ -119,10 +203,6 @@ impl NuveiAuthorizePreprocessingCommon for PaymentsAuthorizeData {
|
|||||||
self.customer_id.clone()
|
self.customer_id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_setup_mandate_details(&self) -> Option<MandateData> {
|
|
||||||
self.setup_mandate_details.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_complete_authorize_url(&self) -> Option<String> {
|
fn get_complete_authorize_url(&self) -> Option<String> {
|
||||||
self.complete_authorize_url.clone()
|
self.complete_authorize_url.clone()
|
||||||
}
|
}
|
||||||
@ -166,7 +246,10 @@ impl NuveiAuthorizePreprocessingCommon for PaymentsAuthorizeData {
|
|||||||
fn get_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>> {
|
fn get_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>> {
|
||||||
self.get_email()
|
self.get_email()
|
||||||
}
|
}
|
||||||
|
fn is_customer_initiated_mandate_payment(&self) -> bool {
|
||||||
|
(self.customer_acceptance.is_some() || self.setup_mandate_details.is_some())
|
||||||
|
&& self.setup_future_usage == Some(FutureUsage::OffSession)
|
||||||
|
}
|
||||||
fn get_is_partial_approval(&self) -> Option<PartialApprovalFlag> {
|
fn get_is_partial_approval(&self) -> Option<PartialApprovalFlag> {
|
||||||
self.enable_partial_authorization
|
self.enable_partial_authorization
|
||||||
.map(PartialApprovalFlag::from)
|
.map(PartialApprovalFlag::from)
|
||||||
@ -192,8 +275,9 @@ impl NuveiAuthorizePreprocessingCommon for PaymentsPreProcessingData {
|
|||||||
fn get_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>> {
|
fn get_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>> {
|
||||||
self.get_email()
|
self.get_email()
|
||||||
}
|
}
|
||||||
fn get_setup_mandate_details(&self) -> Option<MandateData> {
|
fn is_customer_initiated_mandate_payment(&self) -> bool {
|
||||||
self.setup_mandate_details.clone()
|
(self.customer_acceptance.is_some() || self.setup_mandate_details.is_some())
|
||||||
|
&& self.setup_future_usage == Some(FutureUsage::OffSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_complete_authorize_url(&self) -> Option<String> {
|
fn get_complete_authorize_url(&self) -> Option<String> {
|
||||||
@ -1430,6 +1514,19 @@ where
|
|||||||
item.get_optional_shipping().map(|address| address.into());
|
item.get_optional_shipping().map(|address| address.into());
|
||||||
|
|
||||||
let billing_address: Option<BillingAddress> = address.map(|ref address| address.into());
|
let billing_address: Option<BillingAddress> = address.map(|ref address| address.into());
|
||||||
|
|
||||||
|
let device_details = if request_data
|
||||||
|
.device_details
|
||||||
|
.ip_address
|
||||||
|
.clone()
|
||||||
|
.expose()
|
||||||
|
.is_empty()
|
||||||
|
{
|
||||||
|
DeviceDetails::foreign_try_from(&item.request.get_browser_info())?
|
||||||
|
} else {
|
||||||
|
request_data.device_details.clone()
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
is_rebilling: request_data.is_rebilling,
|
is_rebilling: request_data.is_rebilling,
|
||||||
user_token_id: item.customer_id.clone(),
|
user_token_id: item.customer_id.clone(),
|
||||||
@ -1437,9 +1534,7 @@ where
|
|||||||
payment_option: request_data.payment_option,
|
payment_option: request_data.payment_option,
|
||||||
billing_address,
|
billing_address,
|
||||||
shipping_address,
|
shipping_address,
|
||||||
device_details: DeviceDetails::foreign_try_from(
|
device_details,
|
||||||
&item.request.get_browser_info().clone(),
|
|
||||||
)?,
|
|
||||||
url_details: Some(UrlDetails {
|
url_details: Some(UrlDetails {
|
||||||
success_url: return_url.clone(),
|
success_url: return_url.clone(),
|
||||||
failure_url: return_url.clone(),
|
failure_url: return_url.clone(),
|
||||||
@ -1471,59 +1566,41 @@ where
|
|||||||
.and_then(|billing_details| billing_details.address.as_ref());
|
.and_then(|billing_details| billing_details.address.as_ref());
|
||||||
|
|
||||||
if let Some(address) = address {
|
if let Some(address) = address {
|
||||||
// mandatory feilds check
|
// mandatory fields check
|
||||||
address.get_first_name()?;
|
address.get_first_name()?;
|
||||||
item.request.get_email_required()?;
|
item.request.get_email_required()?;
|
||||||
item.get_billing_country()?;
|
item.get_billing_country()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (is_rebilling, additional_params, user_token_id) =
|
let (is_rebilling, additional_params, user_token_id) =
|
||||||
match item.request.get_setup_mandate_details().clone() {
|
match item.request.is_customer_initiated_mandate_payment() {
|
||||||
Some(mandate_data) => {
|
true => {
|
||||||
let details = match mandate_data
|
|
||||||
.mandate_type
|
|
||||||
.get_required_value("mandate_type")
|
|
||||||
.change_context(errors::ConnectorError::MissingRequiredField {
|
|
||||||
field_name: "mandate_type",
|
|
||||||
})? {
|
|
||||||
MandateDataType::SingleUse(details) => details,
|
|
||||||
MandateDataType::MultiUse(details) => {
|
|
||||||
details.ok_or(errors::ConnectorError::MissingRequiredField {
|
|
||||||
field_name: "mandate_data.mandate_type.multi_use",
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mandate_meta: NuveiMandateMeta = utils::to_connector_meta_from_secret(Some(
|
|
||||||
details.get_metadata().ok_or_else(missing_field_err(
|
|
||||||
"mandate_data.mandate_type.{multi_use|single_use}.metadata",
|
|
||||||
))?,
|
|
||||||
))?;
|
|
||||||
(
|
(
|
||||||
Some("0".to_string()), // In case of first installment, rebilling should be 0
|
Some("0".to_string()), // In case of first installment, rebilling should be 0
|
||||||
Some(V2AdditionalParams {
|
Some(V2AdditionalParams {
|
||||||
rebill_expiry: Some(
|
rebill_expiry: Some(
|
||||||
details
|
time::OffsetDateTime::now_utc()
|
||||||
.get_end_date(date_time::DateFormat::YYYYMMDD)
|
.replace_year(time::OffsetDateTime::now_utc().year() + 5)
|
||||||
.change_context(errors::ConnectorError::DateFormattingFailed)?
|
.map_err(|_| errors::ConnectorError::DateFormattingFailed)?
|
||||||
.ok_or_else(missing_field_err(
|
.date()
|
||||||
"mandate_data.mandate_type.{multi_use|single_use}.end_date",
|
.format(&time::macros::format_description!("[year][month][day]"))
|
||||||
))?,
|
.map_err(|_| errors::ConnectorError::DateFormattingFailed)?,
|
||||||
),
|
),
|
||||||
rebill_frequency: Some(mandate_meta.frequency),
|
rebill_frequency: Some("0".to_string()),
|
||||||
challenge_window_size: None,
|
challenge_window_size: Some(CHALLENGE_WINDOW_SIZE.to_string()),
|
||||||
challenge_preference: None,
|
challenge_preference: Some(CHALLENGE_PREFERENCE.to_string()),
|
||||||
}),
|
}),
|
||||||
item.request.get_customer_id_required(),
|
item.request.get_customer_id_required(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// non mandate transactions
|
// non mandate transactions
|
||||||
_ => (
|
false => (
|
||||||
None,
|
None,
|
||||||
Some(V2AdditionalParams {
|
Some(V2AdditionalParams {
|
||||||
rebill_expiry: None,
|
rebill_expiry: None,
|
||||||
rebill_frequency: None,
|
rebill_frequency: None,
|
||||||
challenge_window_size: Some(CHALLENGE_WINDOW_SIZE.to_string()),
|
challenge_window_size: Some(CHALLENGE_WINDOW_SIZE.to_string()),
|
||||||
challenge_preference: Some(CHALLENGE_PREFERNCE.to_string()),
|
challenge_preference: Some(CHALLENGE_PREFERENCE.to_string()),
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
@ -1570,7 +1647,6 @@ where
|
|||||||
three_d,
|
three_d,
|
||||||
card_holder_name: item.get_optional_billing_full_name(),
|
card_holder_name: item.get_optional_billing_full_name(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
is_moto,
|
is_moto,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
@ -2110,6 +2186,66 @@ impl NuveiPaymentsGenericResponse for PSync {}
|
|||||||
impl NuveiPaymentsGenericResponse for Capture {}
|
impl NuveiPaymentsGenericResponse for Capture {}
|
||||||
impl NuveiPaymentsGenericResponse for PostCaptureVoid {}
|
impl NuveiPaymentsGenericResponse for PostCaptureVoid {}
|
||||||
|
|
||||||
|
impl
|
||||||
|
TryFrom<
|
||||||
|
ResponseRouterData<
|
||||||
|
SetupMandate,
|
||||||
|
NuveiPaymentsResponse,
|
||||||
|
SetupMandateRequestData,
|
||||||
|
PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
> for RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(
|
||||||
|
item: ResponseRouterData<
|
||||||
|
SetupMandate,
|
||||||
|
NuveiPaymentsResponse,
|
||||||
|
SetupMandateRequestData,
|
||||||
|
PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let amount = item.data.request.amount;
|
||||||
|
|
||||||
|
let (status, redirection_data, connector_response_data) =
|
||||||
|
process_nuvei_payment_response(&item, amount)?;
|
||||||
|
|
||||||
|
let (amount_captured, minor_amount_capturable) = item.response.get_amount_captured()?;
|
||||||
|
|
||||||
|
let ip_address = item
|
||||||
|
.data
|
||||||
|
.request
|
||||||
|
.browser_info
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "browser_info",
|
||||||
|
})?
|
||||||
|
.ip_address
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "browser_info.ip_address",
|
||||||
|
})?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
status,
|
||||||
|
response: if let Some(err) = build_error_response(&item.response, item.http_code) {
|
||||||
|
Err(err)
|
||||||
|
} else {
|
||||||
|
Ok(create_transaction_response(
|
||||||
|
&item.response,
|
||||||
|
redirection_data,
|
||||||
|
Some(ip_address),
|
||||||
|
)?)
|
||||||
|
},
|
||||||
|
amount_captured,
|
||||||
|
minor_amount_capturable,
|
||||||
|
connector_response: connector_response_data,
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to process Nuvei payment response
|
// Helper function to process Nuvei payment response
|
||||||
|
|
||||||
fn process_nuvei_payment_response<F, T>(
|
fn process_nuvei_payment_response<F, T>(
|
||||||
@ -2160,6 +2296,7 @@ where
|
|||||||
fn create_transaction_response(
|
fn create_transaction_response(
|
||||||
response: &NuveiPaymentsResponse,
|
response: &NuveiPaymentsResponse,
|
||||||
redirection_data: Option<RedirectForm>,
|
redirection_data: Option<RedirectForm>,
|
||||||
|
ip_address: Option<String>,
|
||||||
) -> Result<PaymentsResponseData, error_stack::Report<errors::ConnectorError>> {
|
) -> Result<PaymentsResponseData, error_stack::Report<errors::ConnectorError>> {
|
||||||
Ok(PaymentsResponseData::TransactionResponse {
|
Ok(PaymentsResponseData::TransactionResponse {
|
||||||
resource_id: response
|
resource_id: response
|
||||||
@ -2177,7 +2314,8 @@ fn create_transaction_response(
|
|||||||
.map(|id| MandateReference {
|
.map(|id| MandateReference {
|
||||||
connector_mandate_id: Some(id),
|
connector_mandate_id: Some(id),
|
||||||
payment_method_id: None,
|
payment_method_id: None,
|
||||||
mandate_metadata: None,
|
mandate_metadata: ip_address
|
||||||
|
.map(|ip| pii::SecretSerdeValue::new(serde_json::Value::String(ip))),
|
||||||
connector_mandate_request_reference_id: None,
|
connector_mandate_request_reference_id: None,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@ -2226,6 +2364,14 @@ impl
|
|||||||
process_nuvei_payment_response(&item, amount)?;
|
process_nuvei_payment_response(&item, amount)?;
|
||||||
|
|
||||||
let (amount_captured, minor_amount_capturable) = item.response.get_amount_captured()?;
|
let (amount_captured, minor_amount_capturable) = item.response.get_amount_captured()?;
|
||||||
|
|
||||||
|
let ip_address = item
|
||||||
|
.data
|
||||||
|
.request
|
||||||
|
.browser_info
|
||||||
|
.clone()
|
||||||
|
.and_then(|browser_info| browser_info.ip_address.map(|ip| ip.to_string()));
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
status,
|
status,
|
||||||
response: if let Some(err) = build_error_response(&item.response, item.http_code) {
|
response: if let Some(err) = build_error_response(&item.response, item.http_code) {
|
||||||
@ -2234,6 +2380,7 @@ impl
|
|||||||
Ok(create_transaction_response(
|
Ok(create_transaction_response(
|
||||||
&item.response,
|
&item.response,
|
||||||
redirection_data,
|
redirection_data,
|
||||||
|
ip_address,
|
||||||
)?)
|
)?)
|
||||||
},
|
},
|
||||||
amount_captured,
|
amount_captured,
|
||||||
@ -2273,6 +2420,7 @@ where
|
|||||||
Ok(create_transaction_response(
|
Ok(create_transaction_response(
|
||||||
&item.response,
|
&item.response,
|
||||||
redirection_data,
|
redirection_data,
|
||||||
|
None,
|
||||||
)?)
|
)?)
|
||||||
},
|
},
|
||||||
amount_captured,
|
amount_captured,
|
||||||
@ -2388,11 +2536,26 @@ where
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ip_address = data
|
||||||
|
.recurring_mandate_payment_data
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|r| r.mandate_metadata.as_ref())
|
||||||
|
.ok_or(errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "browser_info.ip_address",
|
||||||
|
})?
|
||||||
|
.clone()
|
||||||
|
.expose()
|
||||||
|
.as_str()
|
||||||
|
.ok_or(errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "browser_info.ip_address",
|
||||||
|
})?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
related_transaction_id,
|
related_transaction_id,
|
||||||
device_details: DeviceDetails::foreign_try_from(
|
device_details: DeviceDetails {
|
||||||
&item.request.get_browser_info().clone(),
|
ip_address: Secret::new(ip_address),
|
||||||
)?,
|
},
|
||||||
is_rebilling: Some("1".to_string()), // In case of second installment, rebilling should be 1
|
is_rebilling: Some("1".to_string()), // In case of second installment, rebilling should be 1
|
||||||
user_token_id: Some(customer_id),
|
user_token_id: Some(customer_id),
|
||||||
payment_option: PaymentOption {
|
payment_option: PaymentOption {
|
||||||
|
|||||||
@ -305,6 +305,38 @@ impl TryFrom<SetupMandateRequestData> for ConnectorCustomerData {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<SetupMandateRequestData> for PaymentsPreProcessingData {
|
||||||
|
type Error = error_stack::Report<ApiErrorResponse>;
|
||||||
|
|
||||||
|
fn try_from(data: SetupMandateRequestData) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
payment_method_data: Some(data.payment_method_data),
|
||||||
|
amount: data.amount,
|
||||||
|
minor_amount: data.minor_amount,
|
||||||
|
email: data.email,
|
||||||
|
currency: Some(data.currency),
|
||||||
|
payment_method_type: data.payment_method_type,
|
||||||
|
setup_mandate_details: data.setup_mandate_details,
|
||||||
|
capture_method: data.capture_method,
|
||||||
|
order_details: None,
|
||||||
|
router_return_url: data.router_return_url,
|
||||||
|
webhook_url: data.webhook_url,
|
||||||
|
complete_authorize_url: data.complete_authorize_url,
|
||||||
|
browser_info: data.browser_info,
|
||||||
|
surcharge_details: None,
|
||||||
|
connector_transaction_id: None,
|
||||||
|
mandate_id: data.mandate_id,
|
||||||
|
related_transaction_id: None,
|
||||||
|
redirect_response: None,
|
||||||
|
enrolled_for_3ds: false,
|
||||||
|
split_payments: None,
|
||||||
|
metadata: data.metadata,
|
||||||
|
customer_acceptance: data.customer_acceptance,
|
||||||
|
setup_future_usage: data.setup_future_usage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
impl
|
impl
|
||||||
TryFrom<
|
TryFrom<
|
||||||
&RouterData<flows::Authorize, PaymentsAuthorizeData, response_types::PaymentsResponseData>,
|
&RouterData<flows::Authorize, PaymentsAuthorizeData, response_types::PaymentsResponseData>,
|
||||||
@ -515,7 +547,8 @@ pub struct PaymentsPreProcessingData {
|
|||||||
pub redirect_response: Option<CompleteAuthorizeRedirectResponse>,
|
pub redirect_response: Option<CompleteAuthorizeRedirectResponse>,
|
||||||
pub metadata: Option<Secret<serde_json::Value>>,
|
pub metadata: Option<Secret<serde_json::Value>>,
|
||||||
pub split_payments: Option<common_types::payments::SplitPaymentsRequest>,
|
pub split_payments: Option<common_types::payments::SplitPaymentsRequest>,
|
||||||
|
pub customer_acceptance: Option<common_payments_types::CustomerAcceptance>,
|
||||||
|
pub setup_future_usage: Option<storage_enums::FutureUsage>,
|
||||||
// New amount for amount frame work
|
// New amount for amount frame work
|
||||||
pub minor_amount: Option<MinorUnit>,
|
pub minor_amount: Option<MinorUnit>,
|
||||||
}
|
}
|
||||||
@ -546,6 +579,8 @@ impl TryFrom<PaymentsAuthorizeData> for PaymentsPreProcessingData {
|
|||||||
enrolled_for_3ds: data.enrolled_for_3ds,
|
enrolled_for_3ds: data.enrolled_for_3ds,
|
||||||
split_payments: data.split_payments,
|
split_payments: data.split_payments,
|
||||||
metadata: data.metadata.map(Secret::new),
|
metadata: data.metadata.map(Secret::new),
|
||||||
|
customer_acceptance: data.customer_acceptance,
|
||||||
|
setup_future_usage: data.setup_future_usage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -576,6 +611,8 @@ impl TryFrom<CompleteAuthorizeData> for PaymentsPreProcessingData {
|
|||||||
split_payments: None,
|
split_payments: None,
|
||||||
enrolled_for_3ds: true,
|
enrolled_for_3ds: true,
|
||||||
metadata: data.connector_meta.map(Secret::new),
|
metadata: data.connector_meta.map(Secret::new),
|
||||||
|
customer_acceptance: data.customer_acceptance,
|
||||||
|
setup_future_usage: data.setup_future_usage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1215,6 +1252,8 @@ pub struct SetupMandateRequestData {
|
|||||||
pub metadata: Option<pii::SecretSerdeValue>,
|
pub metadata: Option<pii::SecretSerdeValue>,
|
||||||
pub complete_authorize_url: Option<String>,
|
pub complete_authorize_url: Option<String>,
|
||||||
pub capture_method: Option<storage_enums::CaptureMethod>,
|
pub capture_method: Option<storage_enums::CaptureMethod>,
|
||||||
|
pub enrolled_for_3ds: bool,
|
||||||
|
pub related_transaction_id: Option<String>,
|
||||||
|
|
||||||
// MinorUnit for amount framework
|
// MinorUnit for amount framework
|
||||||
pub minor_amount: Option<MinorUnit>,
|
pub minor_amount: Option<MinorUnit>,
|
||||||
|
|||||||
@ -19,7 +19,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
routes::SessionState,
|
routes::SessionState,
|
||||||
services,
|
services,
|
||||||
types::{self, api, domain, transformers::ForeignTryFrom},
|
types::{
|
||||||
|
self, api, domain,
|
||||||
|
transformers::{ForeignFrom, ForeignTryFrom},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "v1")]
|
#[cfg(feature = "v1")]
|
||||||
@ -155,6 +158,38 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn add_session_token<'a>(
|
||||||
|
self,
|
||||||
|
state: &SessionState,
|
||||||
|
connector: &api::ConnectorData,
|
||||||
|
) -> RouterResult<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
|
||||||
|
api::AuthorizeSessionToken,
|
||||||
|
types::AuthorizeSessionTokenData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
> = connector.connector.get_connector_integration();
|
||||||
|
let authorize_data = &types::PaymentsAuthorizeSessionTokenRouterData::foreign_from((
|
||||||
|
&self,
|
||||||
|
types::AuthorizeSessionTokenData::foreign_from(&self),
|
||||||
|
));
|
||||||
|
let resp = services::execute_connector_processing_step(
|
||||||
|
state,
|
||||||
|
connector_integration,
|
||||||
|
authorize_data,
|
||||||
|
payments::CallConnectorAction::Trigger,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_payment_failed_response()?;
|
||||||
|
let mut router_data = self;
|
||||||
|
router_data.session_token = resp.session_token;
|
||||||
|
Ok(router_data)
|
||||||
|
}
|
||||||
|
|
||||||
async fn add_payment_method_token<'a>(
|
async fn add_payment_method_token<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
@ -213,6 +248,14 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn preprocessing_steps<'a>(
|
||||||
|
self,
|
||||||
|
state: &SessionState,
|
||||||
|
connector: &api::ConnectorData,
|
||||||
|
) -> RouterResult<Self> {
|
||||||
|
setup_mandate_preprocessing_steps(state, &self, true, connector).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn call_unified_connector_service<'a>(
|
async fn call_unified_connector_service<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
@ -301,3 +344,66 @@ impl mandate::MandateBehaviour for types::SetupMandateRequestData {
|
|||||||
self.customer_acceptance.clone()
|
self.customer_acceptance.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn setup_mandate_preprocessing_steps<F: Clone>(
|
||||||
|
state: &SessionState,
|
||||||
|
router_data: &types::RouterData<F, types::SetupMandateRequestData, types::PaymentsResponseData>,
|
||||||
|
confirm: bool,
|
||||||
|
connector: &api::ConnectorData,
|
||||||
|
) -> RouterResult<types::RouterData<F, types::SetupMandateRequestData, types::PaymentsResponseData>>
|
||||||
|
{
|
||||||
|
if confirm {
|
||||||
|
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
|
||||||
|
api::PreProcessing,
|
||||||
|
types::PaymentsPreProcessingData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
> = connector.connector.get_connector_integration();
|
||||||
|
|
||||||
|
let preprocessing_request_data =
|
||||||
|
types::PaymentsPreProcessingData::try_from(router_data.request.clone())?;
|
||||||
|
|
||||||
|
let preprocessing_response_data: Result<types::PaymentsResponseData, types::ErrorResponse> =
|
||||||
|
Err(types::ErrorResponse::default());
|
||||||
|
|
||||||
|
let preprocessing_router_data =
|
||||||
|
helpers::router_data_type_conversion::<_, api::PreProcessing, _, _, _, _>(
|
||||||
|
router_data.clone(),
|
||||||
|
preprocessing_request_data,
|
||||||
|
preprocessing_response_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
let resp = services::execute_connector_processing_step(
|
||||||
|
state,
|
||||||
|
connector_integration,
|
||||||
|
&preprocessing_router_data,
|
||||||
|
payments::CallConnectorAction::Trigger,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_payment_failed_response()?;
|
||||||
|
|
||||||
|
let mut setup_mandate_router_data = helpers::router_data_type_conversion::<_, F, _, _, _, _>(
|
||||||
|
resp.clone(),
|
||||||
|
router_data.request.to_owned(),
|
||||||
|
resp.response.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if connector.connector_name == api_models::enums::Connector::Nuvei {
|
||||||
|
let (enrolled_for_3ds, related_transaction_id) =
|
||||||
|
match &setup_mandate_router_data.response {
|
||||||
|
Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse {
|
||||||
|
enrolled_v2,
|
||||||
|
related_transaction_id,
|
||||||
|
}) => (*enrolled_v2, related_transaction_id.clone()),
|
||||||
|
_ => (false, None),
|
||||||
|
};
|
||||||
|
setup_mandate_router_data.request.enrolled_for_3ds = enrolled_for_3ds;
|
||||||
|
setup_mandate_router_data.request.related_transaction_id = related_transaction_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(setup_mandate_router_data)
|
||||||
|
} else {
|
||||||
|
Ok(router_data.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1301,6 +1301,8 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>(
|
|||||||
customer_id: None,
|
customer_id: None,
|
||||||
enable_partial_authorization: None,
|
enable_partial_authorization: None,
|
||||||
payment_channel: None,
|
payment_channel: None,
|
||||||
|
enrolled_for_3ds: true,
|
||||||
|
related_transaction_id: None,
|
||||||
};
|
};
|
||||||
let connector_mandate_request_reference_id = payment_data
|
let connector_mandate_request_reference_id = payment_data
|
||||||
.payment_attempt
|
.payment_attempt
|
||||||
@ -5118,6 +5120,8 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::SetupMandateRequ
|
|||||||
customer_id: payment_data.payment_intent.customer_id,
|
customer_id: payment_data.payment_intent.customer_id,
|
||||||
enable_partial_authorization: payment_data.payment_intent.enable_partial_authorization,
|
enable_partial_authorization: payment_data.payment_intent.enable_partial_authorization,
|
||||||
payment_channel: payment_data.payment_intent.payment_channel,
|
payment_channel: payment_data.payment_intent.payment_channel,
|
||||||
|
related_transaction_id: None,
|
||||||
|
enrolled_for_3ds: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5369,6 +5373,8 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPreProce
|
|||||||
enrolled_for_3ds: true,
|
enrolled_for_3ds: true,
|
||||||
split_payments: payment_data.payment_intent.split_payments,
|
split_payments: payment_data.payment_intent.split_payments,
|
||||||
metadata: payment_data.payment_intent.metadata.map(Secret::new),
|
metadata: payment_data.payment_intent.metadata.map(Secret::new),
|
||||||
|
customer_acceptance: payment_data.customer_acceptance,
|
||||||
|
setup_future_usage: payment_data.payment_intent.setup_future_usage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1158,6 +1158,17 @@ impl ForeignFrom<&ExternalVaultProxyPaymentsRouterData> for AuthorizeSessionToke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> ForeignFrom<&'a SetupMandateRouterData> for AuthorizeSessionTokenData {
|
||||||
|
fn foreign_from(data: &'a SetupMandateRouterData) -> Self {
|
||||||
|
Self {
|
||||||
|
amount_to_capture: data.request.amount,
|
||||||
|
currency: data.request.currency,
|
||||||
|
connector_transaction_id: data.payment_id.clone(),
|
||||||
|
amount: data.request.amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Tokenizable {
|
pub trait Tokenizable {
|
||||||
fn set_session_token(&mut self, token: Option<String>);
|
fn set_session_token(&mut self, token: Option<String>);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,25 @@ const successfulThreeDSCardDetails = {
|
|||||||
card_holder_name: "CL-BRW1",
|
card_holder_name: "CL-BRW1",
|
||||||
card_cvc: "123",
|
card_cvc: "123",
|
||||||
};
|
};
|
||||||
|
const singleUseMandateData = {
|
||||||
|
customer_acceptance: customerAcceptance,
|
||||||
|
mandate_type: {
|
||||||
|
single_use: {
|
||||||
|
amount: 8000,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const multiUseMandateData = {
|
||||||
|
customer_acceptance: customerAcceptance,
|
||||||
|
mandate_type: {
|
||||||
|
multi_use: {
|
||||||
|
amount: 8000,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
// Payment method data objects for responses
|
// Payment method data objects for responses
|
||||||
const payment_method_data_no3ds = {
|
const payment_method_data_no3ds = {
|
||||||
card: {
|
card: {
|
||||||
@ -42,31 +61,6 @@ const payment_method_data_no3ds = {
|
|||||||
billing: null,
|
billing: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const payment_method_data_3ds = {
|
|
||||||
card: {
|
|
||||||
last4: "0961",
|
|
||||||
card_type: "CREDIT",
|
|
||||||
card_network: "Visa",
|
|
||||||
card_issuer: "RIVER VALLEY CREDIT UNION",
|
|
||||||
card_issuing_country: "UNITEDSTATES",
|
|
||||||
card_isin: "400002",
|
|
||||||
card_extended_bin: null,
|
|
||||||
card_exp_month: "10",
|
|
||||||
card_exp_year: "25",
|
|
||||||
card_holder_name: "CL-BRW1",
|
|
||||||
payment_checks: {
|
|
||||||
avs_description: null,
|
|
||||||
avs_result_code: "",
|
|
||||||
cvv_2_reply_code: "",
|
|
||||||
cvv_2_description: null,
|
|
||||||
merchant_advice_code: "",
|
|
||||||
merchant_advice_code_description: null,
|
|
||||||
},
|
|
||||||
authentication_data: {},
|
|
||||||
},
|
|
||||||
billing: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const connectorDetails = {
|
export const connectorDetails = {
|
||||||
card_pm: {
|
card_pm: {
|
||||||
// Basic payment intent creation
|
// Basic payment intent creation
|
||||||
@ -204,7 +198,6 @@ export const connectorDetails = {
|
|||||||
body: {
|
body: {
|
||||||
status: "requires_customer_action",
|
status: "requires_customer_action",
|
||||||
setup_future_usage: "on_session",
|
setup_future_usage: "on_session",
|
||||||
payment_method_data: payment_method_data_3ds,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -315,7 +308,25 @@ export const connectorDetails = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ZeroAuthMandate: {
|
||||||
|
Configs: {
|
||||||
|
TRIGGER_SKIP: true,
|
||||||
|
},
|
||||||
|
Request: {
|
||||||
|
payment_method: "card",
|
||||||
|
payment_method_data: {
|
||||||
|
card: successfulNo3DSCardDetails,
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
customer_acceptance: customerAcceptance,
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
status: "succeeded",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
ZeroAuthPaymentIntent: {
|
ZeroAuthPaymentIntent: {
|
||||||
Request: {
|
Request: {
|
||||||
amount: 0,
|
amount: 0,
|
||||||
@ -331,9 +342,6 @@ export const connectorDetails = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ZeroAuthConfirmPayment: {
|
ZeroAuthConfirmPayment: {
|
||||||
Configs: {
|
|
||||||
TRIGGER_SKIP: true,
|
|
||||||
},
|
|
||||||
Request: {
|
Request: {
|
||||||
payment_type: "setup_mandate",
|
payment_type: "setup_mandate",
|
||||||
payment_method: "card",
|
payment_method: "card",
|
||||||
@ -343,11 +351,110 @@ export const connectorDetails = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Response: {
|
Response: {
|
||||||
status: 501,
|
status: 200,
|
||||||
error: {
|
body: {
|
||||||
type: "invalid_request",
|
status: "succeeded",
|
||||||
message: "Setup Mandate flow for Nuvei is not implemented",
|
},
|
||||||
code: "IR_00",
|
},
|
||||||
|
},
|
||||||
|
MITManualCapture: {
|
||||||
|
Configs: {
|
||||||
|
TRIGGER_SKIP: true,
|
||||||
|
},
|
||||||
|
Request: {},
|
||||||
|
Response: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
status: "requires_capture",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MandateSingleUseNo3DSAutoCapture: {
|
||||||
|
Configs: {
|
||||||
|
TRIGGER_SKIP: true,
|
||||||
|
},
|
||||||
|
Request: {
|
||||||
|
payment_method: "card",
|
||||||
|
payment_method_data: {
|
||||||
|
card: successfulNo3DSCardDetails,
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
customer_acceptance: customerAcceptance,
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
status: "succeeded",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MandateMultiUseNo3DSAutoCapture: {
|
||||||
|
Configs: {
|
||||||
|
TRIGGER_SKIP: true,
|
||||||
|
},
|
||||||
|
Request: {
|
||||||
|
payment_method: "card",
|
||||||
|
payment_method_data: {
|
||||||
|
card: successfulNo3DSCardDetails,
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
mandate_data: multiUseMandateData,
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
status: "succeeded",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MandateMultiUseNo3DSManualCapture: {
|
||||||
|
Configs: {
|
||||||
|
TRIGGER_SKIP: true,
|
||||||
|
},
|
||||||
|
Request: {
|
||||||
|
payment_method: "card",
|
||||||
|
payment_method_data: {
|
||||||
|
card: successfulNo3DSCardDetails,
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
mandate_data: multiUseMandateData,
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
status: "requires_capture",
|
||||||
|
payment_method_data: payment_method_data_no3ds,
|
||||||
|
payment_method: "card",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MITAutoCapture: {
|
||||||
|
Request: {
|
||||||
|
amount_to_capture: 6000,
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
status: "succeeded",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -475,9 +582,6 @@ export const connectorDetails = {
|
|||||||
},
|
},
|
||||||
// Payment method ID mandate scenarios
|
// Payment method ID mandate scenarios
|
||||||
PaymentMethodIdMandateNo3DSAutoCapture: {
|
PaymentMethodIdMandateNo3DSAutoCapture: {
|
||||||
Configs: {
|
|
||||||
TRIGGER_SKIP: true,
|
|
||||||
},
|
|
||||||
Request: {
|
Request: {
|
||||||
payment_method: "card",
|
payment_method: "card",
|
||||||
payment_method_data: {
|
payment_method_data: {
|
||||||
|
|||||||
Reference in New Issue
Block a user