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 {
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 {
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> {
HashSet::from([PaymentMethod::Card])
}

View File

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

View File

@ -27,7 +27,7 @@ use url::Url;
use uuid::Uuid;
use crate::{
types::{RefundsResponseRouterData, ResponseRouterData},
types::{CreateOrderResponseRouterData, RefundsResponseRouterData, ResponseRouterData},
utils::{
self, BrowserInformationData, CardData as _, ForeignTryFrom, PaymentsAuthorizeRequestData,
PhoneDetailsData, RouterData as _,
@ -74,23 +74,17 @@ pub struct AirwallexIntentRequest {
order: Option<AirwallexOrderData>,
}
impl TryFrom<&AirwallexRouterData<&types::PaymentsPreProcessingRouterData>>
for AirwallexIntentRequest
{
impl TryFrom<&AirwallexRouterData<&types::CreateOrderRouterData>> for AirwallexIntentRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: &AirwallexRouterData<&types::PaymentsPreProcessingRouterData>,
item: &AirwallexRouterData<&types::CreateOrderRouterData>,
) -> Result<Self, Self::Error> {
let referrer_data = ReferrerData {
r_type: "hyperswitch".to_string(),
version: "1.0.0".to_string(),
};
let amount = item.amount.clone();
let currency = item.router_data.request.currency.ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "currency",
},
)?;
let currency = item.router_data.request.currency;
let order = match item.router_data.request.payment_method_data {
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)]
pub struct AirwallexRouterData<T> {
pub amount: StringMajorUnit,

View File

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

View File

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

View File

@ -6655,14 +6655,6 @@ where
dyn api::Connector:
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
let router_data_and_should_continue_payment = match payment_data.get_payment_method_data() {
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(),
resp.response.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 {
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,