refactor(connector): convert init payment flow to preprocessing flow for nuvei (#4878)

This commit is contained in:
Hrithikesh
2024-06-06 18:51:09 +05:30
committed by GitHub
parent 6750be5aee
commit e7acaa5716
9 changed files with 233 additions and 100 deletions

View File

@ -26,7 +26,6 @@ use crate::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
storage::enums,
transformers::ForeignFrom,
ErrorResponse, Response,
},
utils::ByteSliceExt,
@ -124,6 +123,7 @@ impl api::RefundExecute for Nuvei {}
impl api::RefundSync for Nuvei {}
impl api::PaymentsCompleteAuthorize for Nuvei {}
impl api::ConnectorAccessToken for Nuvei {}
impl api::PaymentsPreProcessing for Nuvei {}
impl
ConnectorIntegration<
@ -524,53 +524,6 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
))
}
async fn execute_pretasks(
&self,
router_data: &mut types::PaymentsAuthorizeRouterData,
app_state: &crate::routes::SessionState,
) -> CustomResult<(), errors::ConnectorError> {
let (enrolled_for_3ds, related_transaction_id) =
match (router_data.auth_type, router_data.payment_method) {
(
diesel_models::enums::AuthenticationType::ThreeDs,
diesel_models::enums::PaymentMethod::Card,
) => {
let integ: Box<
&(dyn ConnectorIntegration<
api::InitPayment,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> + Send
+ Sync
+ 'static),
> = Box::new(&Self);
let init_data = &types::PaymentsInitRouterData::foreign_from((
&router_data.to_owned(),
router_data.request.clone(),
));
let init_resp = services::execute_connector_processing_step(
app_state,
integ,
init_data,
payments::CallConnectorAction::Trigger,
None,
)
.await?;
match init_resp.response {
Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse {
enrolled_v2,
related_transaction_id,
}) => (enrolled_v2, related_transaction_id),
_ => (false, None),
}
}
_ => (false, None),
};
router_data.request.enrolled_for_3ds = enrolled_for_3ds;
router_data.request.related_transaction_id = related_transaction_id;
Ok(())
}
fn get_request_body(
&self,
req: &types::PaymentsAuthorizeRouterData,
@ -725,14 +678,14 @@ impl
impl
ConnectorIntegration<
api::InitPayment,
types::PaymentsAuthorizeData,
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> for Nuvei
{
fn get_headers(
&self,
req: &types::PaymentsInitRouterData,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
self.build_headers(req, connectors)
@ -744,7 +697,7 @@ impl
fn get_url(
&self,
_req: &types::PaymentsInitRouterData,
_req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
@ -755,7 +708,7 @@ impl
fn get_request_body(
&self,
req: &types::PaymentsInitRouterData,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?;
@ -765,16 +718,20 @@ impl
fn build_request(
&self,
req: &types::PaymentsInitRouterData,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsInitType::get_url(self, req, connectors)?)
.url(&types::PaymentsPreProcessingType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(types::PaymentsInitType::get_headers(self, req, connectors)?)
.set_body(types::PaymentsInitType::get_request_body(
.headers(types::PaymentsPreProcessingType::get_headers(
self, req, connectors,
)?)
.set_body(types::PaymentsPreProcessingType::get_request_body(
self, req, connectors,
)?)
.build(),
@ -783,10 +740,10 @@ impl
fn handle_response(
&self,
data: &types::PaymentsInitRouterData,
data: &types::PaymentsPreProcessingRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::PaymentsInitRouterData, errors::ConnectorError> {
) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
let response: nuvei::NuveiPaymentsResponse = res
.response
.parse_struct("NuveiPaymentsResponse")

View File

@ -6,7 +6,7 @@ use common_utils::{
pii::{Email, IpAddress},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::mandates::MandateDataType;
use hyperswitch_domain_models::mandates::{MandateData, MandateDataType};
use masking::{ExposeInterface, PeekInterface, Secret};
use reqwest::Url;
use serde::{Deserialize, Serialize};
@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
use crate::{
connector::utils::{
self, AddressDetailsData, BrowserInformationData, PaymentsAuthorizeRequestData,
PaymentsCancelRequestData, RouterData,
PaymentsCancelRequestData, PaymentsPreProcessingData, RouterData,
},
consts,
core::errors,
@ -23,6 +23,133 @@ use crate::{
utils::OptionExt,
};
trait NuveiAuthorizePreprocessingCommon {
fn get_browser_info(&self) -> Option<BrowserInformation>;
fn get_related_transaction_id(&self) -> Option<String>;
fn get_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>>;
fn get_setup_mandate_details(&self) -> Option<MandateData>;
fn get_complete_authorize_url(&self) -> Option<String>;
fn get_connector_mandate_id(&self) -> Option<String>;
fn get_return_url_required(
&self,
) -> Result<String, error_stack::Report<errors::ConnectorError>>;
fn get_capture_method(&self) -> Option<enums::CaptureMethod>;
fn get_amount_required(&self) -> Result<i64, error_stack::Report<errors::ConnectorError>>;
fn get_currency_required(
&self,
) -> Result<diesel_models::enums::Currency, error_stack::Report<errors::ConnectorError>>;
fn get_payment_method_data_required(
&self,
) -> Result<domain::PaymentMethodData, error_stack::Report<errors::ConnectorError>>;
}
impl NuveiAuthorizePreprocessingCommon for types::PaymentsAuthorizeData {
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_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>> {
self.get_email()
}
fn get_setup_mandate_details(&self) -> Option<MandateData> {
self.setup_mandate_details.clone()
}
fn get_complete_authorize_url(&self) -> Option<String> {
self.complete_authorize_url.clone()
}
fn get_connector_mandate_id(&self) -> Option<String> {
self.connector_mandate_id().clone()
}
fn get_return_url_required(
&self,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
self.get_return_url()
}
fn get_capture_method(&self) -> Option<enums::CaptureMethod> {
self.capture_method
}
fn get_amount_required(&self) -> Result<i64, error_stack::Report<errors::ConnectorError>> {
Ok(self.amount)
}
fn get_currency_required(
&self,
) -> Result<diesel_models::enums::Currency, error_stack::Report<errors::ConnectorError>> {
Ok(self.currency)
}
fn get_payment_method_data_required(
&self,
) -> Result<domain::PaymentMethodData, error_stack::Report<errors::ConnectorError>> {
Ok(self.payment_method_data.clone())
}
}
impl NuveiAuthorizePreprocessingCommon for types::PaymentsPreProcessingData {
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_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>> {
self.get_email()
}
fn get_setup_mandate_details(&self) -> Option<MandateData> {
self.setup_mandate_details.clone()
}
fn get_complete_authorize_url(&self) -> Option<String> {
self.complete_authorize_url.clone()
}
fn get_connector_mandate_id(&self) -> Option<String> {
self.connector_mandate_id()
}
fn get_return_url_required(
&self,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
self.get_return_url()
}
fn get_capture_method(&self) -> Option<enums::CaptureMethod> {
self.capture_method
}
fn get_amount_required(&self) -> Result<i64, error_stack::Report<errors::ConnectorError>> {
self.get_amount()
}
fn get_currency_required(
&self,
) -> Result<diesel_models::enums::Currency, error_stack::Report<errors::ConnectorError>> {
self.get_currency()
}
fn get_payment_method_data_required(
&self,
) -> Result<domain::PaymentMethodData, error_stack::Report<errors::ConnectorError>> {
self.payment_method_data.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "payment_method_data",
}
.into(),
)
}
}
#[derive(Debug, Serialize, Default, Deserialize)]
pub struct NuveiMeta {
pub session_token: Secret<String>,
@ -628,26 +755,28 @@ impl TryFrom<common_enums::enums::BankNames> for NuveiBIC {
}
}
impl<F>
impl<F, Req>
ForeignTryFrom<(
AlternativePaymentMethodType,
Option<domain::BankRedirectData>,
&types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
&types::RouterData<F, Req, types::PaymentsResponseData>,
)> for NuveiPaymentsRequest
where
Req: NuveiAuthorizePreprocessingCommon,
{
type Error = error_stack::Report<errors::ConnectorError>;
fn foreign_try_from(
data: (
AlternativePaymentMethodType,
Option<domain::BankRedirectData>,
&types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
&types::RouterData<F, Req, 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: item.request.get_email()?,
email: item.request.get_email_required()?,
country: item.get_billing_country()?,
..Default::default()
}),
@ -655,7 +784,7 @@ impl<F>
),
(AlternativePaymentMethodType::Giropay, _) => (
Some(BillingAddress {
email: item.request.get_email()?,
email: item.request.get_email_required()?,
country: item.get_billing_country()?,
..Default::default()
}),
@ -668,7 +797,7 @@ impl<F>
Some(BillingAddress {
first_name: Some(first_name.clone()),
last_name: Some(address.get_last_name().unwrap_or(first_name).clone()),
email: item.request.get_email()?,
email: item.request.get_email_required()?,
country: item.get_billing_country()?,
}),
None,
@ -686,7 +815,7 @@ impl<F>
last_name: Some(
address.get_last_name().ok().unwrap_or(&first_name).clone(),
),
email: item.request.get_email()?,
email: item.request.get_email_required()?,
country: item.get_billing_country()?,
}),
bank_name.map(NuveiBIC::try_from).transpose()?,
@ -710,10 +839,13 @@ impl<F>
}
}
fn get_pay_later_info<F>(
fn get_pay_later_info<F, Req>(
payment_method_type: AlternativePaymentMethodType,
item: &types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
) -> Result<NuveiPaymentsRequest, error_stack::Report<errors::ConnectorError>> {
item: &types::RouterData<F, Req, types::PaymentsResponseData>,
) -> Result<NuveiPaymentsRequest, error_stack::Report<errors::ConnectorError>>
where
Req: NuveiAuthorizePreprocessingCommon,
{
let address = item
.get_billing()?
.address
@ -728,7 +860,7 @@ fn get_pay_later_info<F>(
..Default::default()
}),
billing_address: Some(BillingAddress {
email: item.request.get_email()?,
email: item.request.get_email_required()?,
first_name: Some(first_name.clone()),
last_name: Some(address.get_last_name().unwrap_or(first_name).clone()),
country: address.get_country()?.to_owned(),
@ -739,21 +871,23 @@ fn get_pay_later_info<F>(
})
}
impl<F>
impl<F, Req>
TryFrom<(
&types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
&types::RouterData<F, Req, types::PaymentsResponseData>,
String,
)> for NuveiPaymentsRequest
where
Req: NuveiAuthorizePreprocessingCommon,
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
data: (
&types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
&types::RouterData<F, Req, types::PaymentsResponseData>,
String,
),
) -> Result<Self, Self::Error> {
let item = data.0;
let request_data = match item.request.payment_method_data.clone() {
let request_data = match item.request.get_payment_method_data_required()?.clone() {
domain::PaymentMethodData::Card(card) => get_card_info(item, &card),
domain::PaymentMethodData::MandatePayment => Self::try_from(item),
domain::PaymentMethodData::Wallet(wallet) => match wallet {
@ -866,16 +1000,17 @@ impl<F>
.into())
}
}?;
let currency = item.request.get_currency_required()?;
let request = Self::try_from(NuveiPaymentRequestData {
amount: utils::to_currency_base_unit(item.request.amount, item.request.currency)?,
currency: item.request.currency,
amount: utils::to_currency_base_unit(item.request.get_amount_required()?, currency)?,
currency,
connector_auth_type: item.connector_auth_type.clone(),
client_request_id: item.connector_request_reference_id.clone(),
session_token: Secret::new(data.1),
capture_method: item.request.capture_method,
capture_method: item.request.get_capture_method(),
..Default::default()
})?;
let return_url = item.request.get_return_url()?;
let return_url = item.request.get_return_url_required()?;
Ok(Self {
is_rebilling: request_data.is_rebilling,
user_token_id: request_data.user_token_id,
@ -893,13 +1028,16 @@ impl<F>
}
}
fn get_card_info<F>(
item: &types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
fn get_card_info<F, Req>(
item: &types::RouterData<F, Req, types::PaymentsResponseData>,
card_details: &domain::Card,
) -> Result<NuveiPaymentsRequest, error_stack::Report<errors::ConnectorError>> {
let browser_information = item.request.browser_info.clone();
) -> Result<NuveiPaymentsRequest, error_stack::Report<errors::ConnectorError>>
where
Req: NuveiAuthorizePreprocessingCommon,
{
let browser_information = item.request.get_browser_info().clone();
let related_transaction_id = if item.is_three_ds() {
item.request.related_transaction_id.clone()
item.request.get_related_transaction_id().clone()
} else {
None
};
@ -914,14 +1052,14 @@ fn get_card_info<F>(
Some(BillingAddress {
first_name: Some(first_name.clone()),
last_name: Some(address.get_last_name().ok().unwrap_or(&first_name).clone()),
email: item.request.get_email()?,
email: item.request.get_email_required()?,
country: item.get_billing_country()?,
})
}
None => None,
};
let (is_rebilling, additional_params, user_token_id) =
match item.request.setup_mandate_details.clone() {
match item.request.get_setup_mandate_details().clone() {
Some(mandate_data) => {
let details = match mandate_data
.mandate_type
@ -955,7 +1093,7 @@ fn get_card_info<F>(
rebill_frequency: Some(mandate_meta.frequency),
challenge_window_size: None,
}),
Some(item.request.get_email()?),
Some(item.request.get_email_required()?),
)
}
_ => (None, None, None),
@ -982,7 +1120,7 @@ fn get_card_info<F>(
Some(ThreeD {
browser_details,
v2_additional_params: additional_params,
notification_url: item.request.complete_authorize_url.clone(),
notification_url: item.request.get_complete_authorize_url().clone(),
merchant_url: item.return_url.clone(),
platform_type: Some(PlatformType::Browser),
method_completion_ind: Some(MethodCompletion::Unavailable),
@ -997,7 +1135,7 @@ fn get_card_info<F>(
is_rebilling,
user_token_id,
device_details: Option::<DeviceDetails>::foreign_try_from(
&item.request.browser_info.clone(),
&item.request.get_browser_info().clone(),
)?,
payment_option: PaymentOption::from(NuveiCardDetails {
card: card_details.clone(),
@ -1479,12 +1617,12 @@ where
}
}
impl TryFrom<types::PaymentsInitResponseRouterData<NuveiPaymentsResponse>>
for types::PaymentsInitRouterData
impl TryFrom<types::PaymentsPreprocessingResponseRouterData<NuveiPaymentsResponse>>
for types::PaymentsPreProcessingRouterData
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::PaymentsInitResponseRouterData<NuveiPaymentsResponse>,
item: types::PaymentsPreprocessingResponseRouterData<NuveiPaymentsResponse>,
) -> Result<Self, Self::Error> {
let response = item.response;
let is_enrolled_for_3ds = response
@ -1558,28 +1696,30 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, NuveiPaymentsResponse>
}
}
impl<F> TryFrom<&types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>>
impl<F, Req> TryFrom<&types::RouterData<F, Req, types::PaymentsResponseData>>
for NuveiPaymentsRequest
where
Req: NuveiAuthorizePreprocessingCommon,
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
data: &types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
data: &types::RouterData<F, Req, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
{
let item = data;
let connector_mandate_id = &item.request.connector_mandate_id();
let connector_mandate_id = &item.request.get_connector_mandate_id();
let related_transaction_id = if item.is_three_ds() {
item.request.related_transaction_id.clone()
item.request.get_related_transaction_id().clone()
} else {
None
};
Ok(Self {
related_transaction_id,
device_details: Option::<DeviceDetails>::foreign_try_from(
&item.request.browser_info.clone(),
&item.request.get_browser_info().clone(),
)?,
is_rebilling: Some("1".to_string()), // In case of second installment, rebilling should be 1
user_token_id: Some(item.request.get_email()?),
user_token_id: Some(item.request.get_email_required()?),
payment_option: PaymentOption {
user_payment_option_id: connector_mandate_id.clone(),
..Default::default()

View File

@ -615,6 +615,7 @@ pub trait PaymentsPreProcessingData {
fn get_return_url(&self) -> Result<String, Error>;
fn get_browser_info(&self) -> Result<BrowserInformation, Error>;
fn get_complete_authorize_url(&self) -> Result<String, Error>;
fn connector_mandate_id(&self) -> Option<String>;
}
impl PaymentsPreProcessingData for types::PaymentsPreProcessingData {
@ -664,6 +665,16 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData {
.clone()
.ok_or_else(missing_field_err("complete_authorize_url"))
}
fn connector_mandate_id(&self) -> Option<String> {
self.mandate_id
.as_ref()
.and_then(|mandate_ids| match &mandate_ids.mandate_reference_id {
Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => {
connector_mandate_ids.connector_mandate_id.clone()
}
_ => None,
})
}
}
pub trait PaymentsCaptureRequestData {

View File

@ -1965,6 +1965,12 @@ where
) && router_data.status
!= common_enums::AttemptStatus::AuthenticationFailed;
(router_data, should_continue)
} else if connector.connector_name == router_types::Connector::Nuvei
&& router_data.auth_type == common_enums::AuthenticationType::ThreeDs
&& !is_operation_complete_authorize(&operation)
{
router_data = router_data.preprocessing_steps(state, connector).await?;
(router_data, should_continue_payment)
} else {
(router_data, should_continue_payment)
}

View File

@ -978,7 +978,6 @@ default_imp_for_pre_processing_steps!(
connector::Netcetera,
connector::Nexinets,
connector::Noon,
connector::Nuvei,
connector::Opayo,
connector::Opennode,
connector::Payeezy,

View File

@ -361,6 +361,16 @@ pub async fn authorize_preprocessing_steps<F: Clone>(
);
if connector.connector_name == api_models::enums::Connector::Airwallex {
authorize_router_data.reference_id = resp.reference_id;
} else if connector.connector_name == api_models::enums::Connector::Nuvei {
let (enrolled_for_3ds, related_transaction_id) = match &authorize_router_data.response {
Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse {
enrolled_v2,
related_transaction_id,
}) => (*enrolled_v2, related_transaction_id.clone()),
_ => (false, None),
};
authorize_router_data.request.enrolled_for_3ds = enrolled_for_3ds;
authorize_router_data.request.related_transaction_id = related_transaction_id;
}
Ok(authorize_router_data)
} else {

View File

@ -1797,6 +1797,8 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPreProce
surcharge_details: payment_data.surcharge_details,
connector_transaction_id: payment_data.payment_attempt.connector_transaction_id,
redirect_response: None,
mandate_id: payment_data.mandate_id,
related_transaction_id: None,
})
}
}

View File

@ -120,6 +120,8 @@ pub type PaymentsInitResponseRouterData<R> =
ResponseRouterData<api::InitPayment, R, PaymentsAuthorizeData, PaymentsResponseData>;
pub type PaymentsCaptureResponseRouterData<R> =
ResponseRouterData<api::Capture, R, PaymentsCaptureData, PaymentsResponseData>;
pub type PaymentsPreprocessingResponseRouterData<R> =
ResponseRouterData<api::PreProcessing, R, PaymentsPreProcessingData, PaymentsResponseData>;
pub type TokenizationResponseRouterData<R> = ResponseRouterData<
api::PaymentMethodToken,
R,