mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
fix(payments): update preprocessing steps and handle session token in payment flow (#9947)
This commit is contained in:
@ -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])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user