fix(payments): update preprocessing steps and handle session token in payment flow (#9947)

This commit is contained in:
Ayush Anand
2025-10-27 11:50:04 +05:30
committed by GitHub
parent faed8d352c
commit 1c037fc717
7 changed files with 72 additions and 72 deletions

View File

@ -426,7 +426,10 @@ impl Connector {
) )
} }
pub fn requires_order_creation_before_payment(self, payment_method: PaymentMethod) -> bool { pub fn requires_order_creation_before_payment(self, payment_method: PaymentMethod) -> bool {
matches!((self, payment_method), (Self::Razorpay, PaymentMethod::Upi)) matches!(
(self, payment_method),
(Self::Razorpay, PaymentMethod::Upi) | (Self::Airwallex, PaymentMethod::Card)
)
} }
pub fn supports_file_storage_module(self) -> bool { pub fn supports_file_storage_module(self) -> bool {
matches!(self, Self::Stripe | Self::Checkout | Self::Worldpayvantiv) matches!(self, Self::Stripe | Self::Checkout | Self::Worldpayvantiv)
@ -575,10 +578,6 @@ impl Connector {
} }
} }
pub fn is_pre_processing_required_before_authorize(self) -> bool {
matches!(self, Self::Airwallex)
}
pub fn get_payment_methods_supporting_extended_authorization(self) -> HashSet<PaymentMethod> { pub fn get_payment_methods_supporting_extended_authorization(self) -> HashSet<PaymentMethod> {
HashSet::from([PaymentMethod::Card]) HashSet::from([PaymentMethod::Card])
} }

View File

@ -19,23 +19,26 @@ use hyperswitch_domain_models::{
router_data::{AccessToken, ErrorResponse, RouterData}, router_data::{AccessToken, ErrorResponse, RouterData},
router_flow_types::{ router_flow_types::{
access_token_auth::AccessTokenAuth, access_token_auth::AccessTokenAuth,
payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, payments::{
Authorize, Capture, CreateOrder, PSync, PaymentMethodToken, Session, SetupMandate, Void,
},
refunds::{Execute, RSync}, refunds::{Execute, RSync},
CompleteAuthorize, PreProcessing, CompleteAuthorize,
}, },
router_request_types::{ router_request_types::{
AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, AccessTokenRequestData, CompleteAuthorizeData, CreateOrderRequestData,
PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData,
PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData,
SetupMandateRequestData,
}, },
router_response_types::{ router_response_types::{
ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData,
SupportedPaymentMethods, SupportedPaymentMethodsExt, SupportedPaymentMethods, SupportedPaymentMethodsExt,
}, },
types::{ types::{
PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, CreateOrderRouterData, PaymentsAuthorizeRouterData, PaymentsCancelRouterData,
PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData, PaymentsCaptureRouterData, PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData,
PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData,
}, },
}; };
use hyperswitch_interfaces::{ use hyperswitch_interfaces::{
@ -47,7 +50,7 @@ use hyperswitch_interfaces::{
disputes::DisputePayload, disputes::DisputePayload,
errors, errors,
events::connector_api_logs::ConnectorEvent, events::connector_api_logs::ConnectorEvent,
types::{self, Response}, types::{self, CreateOrderType, Response},
webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, webhooks::{IncomingWebhook, IncomingWebhookRequestDetails},
}; };
use masking::{Mask, PeekInterface}; use masking::{Mask, PeekInterface};
@ -58,7 +61,10 @@ use crate::{
connectors::airwallex::transformers::AirwallexAuthorizeResponse, connectors::airwallex::transformers::AirwallexAuthorizeResponse,
constants::headers, constants::headers,
types::{RefreshTokenRouterData, ResponseRouterData}, types::{RefreshTokenRouterData, ResponseRouterData},
utils::{convert_amount, AccessTokenRequestInfo, ForeignTryFrom, RefundsRequestData}, utils::{
convert_amount, AccessTokenRequestInfo, ForeignTryFrom, PaymentsAuthorizeRequestData,
RefundsRequestData,
},
}; };
#[derive(Clone)] #[derive(Clone)]
@ -154,8 +160,8 @@ impl ConnectorCommon for Airwallex {
impl ConnectorValidation for Airwallex {} impl ConnectorValidation for Airwallex {}
impl api::Payment for Airwallex {} impl api::Payment for Airwallex {}
impl api::PaymentsPreProcessing for Airwallex {}
impl api::PaymentsCompleteAuthorize for Airwallex {} impl api::PaymentsCompleteAuthorize for Airwallex {}
impl api::PaymentsCreateOrder for Airwallex {}
impl api::MandateSetup for Airwallex {} impl api::MandateSetup for Airwallex {}
impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
for Airwallex for Airwallex
@ -262,12 +268,10 @@ impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken>
} }
} }
impl ConnectorIntegration<PreProcessing, PaymentsPreProcessingData, PaymentsResponseData> impl ConnectorIntegration<CreateOrder, CreateOrderRequestData, PaymentsResponseData> for Airwallex {
for Airwallex
{
fn get_headers( fn get_headers(
&self, &self,
req: &PaymentsPreProcessingRouterData, req: &CreateOrderRouterData,
connectors: &Connectors, connectors: &Connectors,
) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> { ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
self.build_headers(req, connectors) self.build_headers(req, connectors)
@ -279,7 +283,7 @@ impl ConnectorIntegration<PreProcessing, PaymentsPreProcessingData, PaymentsResp
fn get_url( fn get_url(
&self, &self,
_req: &PaymentsPreProcessingRouterData, _req: &CreateOrderRouterData,
connectors: &Connectors, connectors: &Connectors,
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
Ok(format!( Ok(format!(
@ -291,22 +295,13 @@ impl ConnectorIntegration<PreProcessing, PaymentsPreProcessingData, PaymentsResp
fn get_request_body( fn get_request_body(
&self, &self,
req: &PaymentsPreProcessingRouterData, req: &CreateOrderRouterData,
_connectors: &Connectors, _connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let amount_in_minor_unit = MinorUnit::new(req.request.amount.ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "amount",
},
)?);
let amount = convert_amount( let amount = convert_amount(
self.amount_converter, self.amount_converter,
amount_in_minor_unit, req.request.minor_amount,
req.request req.request.currency,
.currency
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "currency",
})?,
)?; )?;
let connector_router_data = airwallex::AirwallexRouterData::try_from((amount, req))?; let connector_router_data = airwallex::AirwallexRouterData::try_from((amount, req))?;
let connector_req = airwallex::AirwallexIntentRequest::try_from(&connector_router_data)?; let connector_req = airwallex::AirwallexIntentRequest::try_from(&connector_router_data)?;
@ -315,35 +310,29 @@ impl ConnectorIntegration<PreProcessing, PaymentsPreProcessingData, PaymentsResp
fn build_request( fn build_request(
&self, &self,
req: &PaymentsPreProcessingRouterData, req: &CreateOrderRouterData,
connectors: &Connectors, connectors: &Connectors,
) -> CustomResult<Option<Request>, errors::ConnectorError> { ) -> CustomResult<Option<Request>, errors::ConnectorError> {
Ok(Some( Ok(Some(
RequestBuilder::new() RequestBuilder::new()
.method(Method::Post) .method(Method::Post)
.url(&types::PaymentsPreProcessingType::get_url( .url(&CreateOrderType::get_url(self, req, connectors)?)
self, req, connectors,
)?)
.attach_default_headers() .attach_default_headers()
.headers(types::PaymentsPreProcessingType::get_headers( .headers(CreateOrderType::get_headers(self, req, connectors)?)
self, req, connectors, .set_body(CreateOrderType::get_request_body(self, req, connectors)?)
)?)
.set_body(types::PaymentsPreProcessingType::get_request_body(
self, req, connectors,
)?)
.build(), .build(),
)) ))
} }
fn handle_response( fn handle_response(
&self, &self,
data: &PaymentsPreProcessingRouterData, data: &CreateOrderRouterData,
event_builder: Option<&mut ConnectorEvent>, event_builder: Option<&mut ConnectorEvent>,
res: Response, res: Response,
) -> CustomResult<PaymentsPreProcessingRouterData, errors::ConnectorError> { ) -> CustomResult<CreateOrderRouterData, errors::ConnectorError> {
let response: airwallex::AirwallexPaymentsResponse = res let response: airwallex::AirwallexOrderResponse = res
.response .response
.parse_struct("airwallex AirwallexPaymentsResponse") .parse_struct("airwallex AirwallexOrderResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response)); event_builder.map(|i| i.set_response_body(&response));
@ -391,9 +380,7 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
"{}{}{}{}", "{}{}{}{}",
self.base_url(connectors), self.base_url(connectors),
"api/v1/pa/payment_intents/", "api/v1/pa/payment_intents/",
req.reference_id req.request.get_order_id()?,
.clone()
.ok_or(errors::ConnectorError::MissingConnectorTransactionID)?,
"/confirm" "/confirm"
)) ))
} }

View File

@ -27,7 +27,7 @@ use url::Url;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
types::{RefundsResponseRouterData, ResponseRouterData}, types::{CreateOrderResponseRouterData, RefundsResponseRouterData, ResponseRouterData},
utils::{ utils::{
self, BrowserInformationData, CardData as _, ForeignTryFrom, PaymentsAuthorizeRequestData, self, BrowserInformationData, CardData as _, ForeignTryFrom, PaymentsAuthorizeRequestData,
PhoneDetailsData, RouterData as _, PhoneDetailsData, RouterData as _,
@ -74,23 +74,17 @@ pub struct AirwallexIntentRequest {
order: Option<AirwallexOrderData>, order: Option<AirwallexOrderData>,
} }
impl TryFrom<&AirwallexRouterData<&types::PaymentsPreProcessingRouterData>> impl TryFrom<&AirwallexRouterData<&types::CreateOrderRouterData>> for AirwallexIntentRequest {
for AirwallexIntentRequest
{
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from( fn try_from(
item: &AirwallexRouterData<&types::PaymentsPreProcessingRouterData>, item: &AirwallexRouterData<&types::CreateOrderRouterData>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
let referrer_data = ReferrerData { let referrer_data = ReferrerData {
r_type: "hyperswitch".to_string(), r_type: "hyperswitch".to_string(),
version: "1.0.0".to_string(), version: "1.0.0".to_string(),
}; };
let amount = item.amount.clone(); let amount = item.amount.clone();
let currency = item.router_data.request.currency.ok_or( let currency = item.router_data.request.currency;
errors::ConnectorError::MissingRequiredField {
field_name: "currency",
},
)?;
let order = match item.router_data.request.payment_method_data { let order = match item.router_data.request.payment_method_data {
Some(PaymentMethodData::PayLater(_)) => Some( Some(PaymentMethodData::PayLater(_)) => Some(
@ -137,6 +131,30 @@ impl TryFrom<&AirwallexRouterData<&types::PaymentsPreProcessingRouterData>>
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AirwallexOrderResponse {
pub status: AirwallexPaymentStatus,
pub id: String,
pub payment_consent_id: Option<Secret<String>>,
pub next_action: Option<AirwallexPaymentsNextAction>,
}
impl TryFrom<CreateOrderResponseRouterData<AirwallexOrderResponse>>
for types::CreateOrderRouterData
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: CreateOrderResponseRouterData<AirwallexOrderResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(PaymentsResponseData::PaymentsCreateOrderResponse {
order_id: item.response.id.clone(),
}),
..item.data
})
}
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct AirwallexRouterData<T> { pub struct AirwallexRouterData<T> {
pub amount: StringMajorUnit, pub amount: StringMajorUnit,

View File

@ -768,7 +768,6 @@ default_imp_for_create_order!(
connectors::Adyen, connectors::Adyen,
connectors::Adyenplatform, connectors::Adyenplatform,
connectors::Affirm, connectors::Affirm,
connectors::Airwallex,
connectors::Amazonpay, connectors::Amazonpay,
connectors::Archipel, connectors::Archipel,
connectors::Authipay, connectors::Authipay,
@ -2359,6 +2358,7 @@ default_imp_for_pre_processing_steps!(
connectors::Aci, connectors::Aci,
connectors::Adyenplatform, connectors::Adyenplatform,
connectors::Affirm, connectors::Affirm,
connectors::Airwallex,
connectors::Amazonpay, connectors::Amazonpay,
connectors::Archipel, connectors::Archipel,
connectors::Authipay, connectors::Authipay,

View File

@ -517,6 +517,8 @@ impl TryFrom<ExternalVaultProxyPaymentsData> for PaymentMethodTokenizationData {
pub struct CreateOrderRequestData { pub struct CreateOrderRequestData {
pub minor_amount: MinorUnit, pub minor_amount: MinorUnit,
pub currency: storage_enums::Currency, pub currency: storage_enums::Currency,
pub payment_method_data: Option<PaymentMethodData>,
pub order_details: Option<Vec<OrderDetailsWithAmount>>,
} }
impl TryFrom<PaymentsAuthorizeData> for CreateOrderRequestData { impl TryFrom<PaymentsAuthorizeData> for CreateOrderRequestData {
@ -526,6 +528,8 @@ impl TryFrom<PaymentsAuthorizeData> for CreateOrderRequestData {
Ok(Self { Ok(Self {
minor_amount: data.minor_amount, minor_amount: data.minor_amount,
currency: data.currency, currency: data.currency,
payment_method_data: Some(data.payment_method_data),
order_details: data.order_details,
}) })
} }
} }
@ -537,6 +541,8 @@ impl TryFrom<ExternalVaultProxyPaymentsData> for CreateOrderRequestData {
Ok(Self { Ok(Self {
minor_amount: data.minor_amount, minor_amount: data.minor_amount,
currency: data.currency, currency: data.currency,
payment_method_data: None,
order_details: data.order_details,
}) })
} }
} }

View File

@ -6655,14 +6655,6 @@ where
dyn api::Connector: dyn api::Connector:
services::api::ConnectorIntegration<F, Req, router_types::PaymentsResponseData>, services::api::ConnectorIntegration<F, Req, router_types::PaymentsResponseData>,
{ {
if !is_operation_complete_authorize(&operation)
&& connector
.connector_name
.is_pre_processing_required_before_authorize()
{
router_data = router_data.preprocessing_steps(state, connector).await?;
return Ok((router_data, should_continue_payment));
}
//TODO: For ACH transfers, if preprocessing_step is not required for connectors encountered in future, add the check //TODO: For ACH transfers, if preprocessing_step is not required for connectors encountered in future, add the check
let router_data_and_should_continue_payment = match payment_data.get_payment_method_data() { let router_data_and_should_continue_payment = match payment_data.get_payment_method_data() {
Some(domain::PaymentMethodData::BankTransfer(_)) => (router_data, should_continue_payment), Some(domain::PaymentMethodData::BankTransfer(_)) => (router_data, should_continue_payment),

View File

@ -711,9 +711,7 @@ pub async fn authorize_preprocessing_steps<F: Clone>(
router_data.request.to_owned(), router_data.request.to_owned(),
resp.response.clone(), resp.response.clone(),
); );
if connector.connector_name == api_models::enums::Connector::Airwallex { if connector.connector_name == api_models::enums::Connector::Nuvei {
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 { let (enrolled_for_3ds, related_transaction_id) = match &authorize_router_data.response {
Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse {
enrolled_v2, enrolled_v2,