diff --git a/crates/hyperswitch_connectors/src/connectors/helcim.rs b/crates/hyperswitch_connectors/src/connectors/helcim.rs index 05165fbe91..4845b2cbc7 100644 --- a/crates/hyperswitch_connectors/src/connectors/helcim.rs +++ b/crates/hyperswitch_connectors/src/connectors/helcim.rs @@ -924,7 +924,7 @@ impl ConnectorTransactionId for Helcim { #[cfg(feature = "v1")] fn connector_transaction_id( &self, - payment_attempt: PaymentAttempt, + payment_attempt: &PaymentAttempt, ) -> Result, ApiErrorResponse> { if payment_attempt.get_connector_payment_id().is_none() { let metadata = @@ -940,7 +940,7 @@ impl ConnectorTransactionId for Helcim { #[cfg(feature = "v2")] fn connector_transaction_id( &self, - payment_attempt: PaymentAttempt, + payment_attempt: &PaymentAttempt, ) -> Result, ApiErrorResponse> { use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; diff --git a/crates/hyperswitch_connectors/src/connectors/nexinets.rs b/crates/hyperswitch_connectors/src/connectors/nexinets.rs index 08084973c5..8733bede9c 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexinets.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexinets.rs @@ -886,7 +886,7 @@ impl ConnectorTransactionId for Nexinets { #[cfg(feature = "v1")] fn connector_transaction_id( &self, - payment_attempt: PaymentAttempt, + payment_attempt: &PaymentAttempt, ) -> Result, ApiErrorResponse> { let metadata = Self::connector_transaction_id(self, payment_attempt.connector_metadata.as_ref()); @@ -896,7 +896,7 @@ impl ConnectorTransactionId for Nexinets { #[cfg(feature = "v2")] fn connector_transaction_id( &self, - payment_attempt: PaymentAttempt, + payment_attempt: &PaymentAttempt, ) -> Result, ApiErrorResponse> { use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; diff --git a/crates/hyperswitch_connectors/src/connectors/paypal.rs b/crates/hyperswitch_connectors/src/connectors/paypal.rs index 578baba88b..f90ac021fb 100644 --- a/crates/hyperswitch_connectors/src/connectors/paypal.rs +++ b/crates/hyperswitch_connectors/src/connectors/paypal.rs @@ -20,8 +20,8 @@ use hyperswitch_domain_models::{ router_flow_types::{ access_token_auth::AccessTokenAuth, payments::{ - Authorize, Capture, PSync, PaymentMethodToken, PostSessionTokens, PreProcessing, - SdkSessionUpdate, Session, SetupMandate, Void, + Authorize, Capture, IncrementalAuthorization, PSync, PaymentMethodToken, + PostSessionTokens, PreProcessing, SdkSessionUpdate, Session, SetupMandate, Void, }, refunds::{Execute, RSync}, CompleteAuthorize, VerifyWebhookSource, @@ -29,19 +29,19 @@ use hyperswitch_domain_models::{ router_request_types::{ AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, - PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, - PaymentsSyncData, RefundsData, ResponseId, SdkPaymentsSessionUpdateData, - SetupMandateRequestData, VerifyWebhookSourceRequestData, + PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData, + PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, RefundsData, ResponseId, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ PaymentsResponseData, RefundsResponseData, VerifyWebhookSourceResponseData, }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsCompleteAuthorizeRouterData, PaymentsPostSessionTokensRouterData, - PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefreshTokenRouterData, - RefundSyncRouterData, RefundsRouterData, SdkSessionUpdateRouterData, - SetupMandateRouterData, VerifyWebhookSourceRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsIncrementalAuthorizationRouterData, + PaymentsPostSessionTokensRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData, + SdkSessionUpdateRouterData, SetupMandateRouterData, VerifyWebhookSourceRouterData, }, }; #[cfg(feature = "payouts")] @@ -55,17 +55,17 @@ use hyperswitch_interfaces::types::{PayoutFulfillType, PayoutSyncType}; use hyperswitch_interfaces::{ api::{ self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, - ConnectorSpecifications, ConnectorValidation, + ConnectorSpecifications, ConnectorValidation, PaymentIncrementalAuthorization, }, configs::Connectors, consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, disputes, errors, events::connector_api_logs::ConnectorEvent, types::{ - PaymentsAuthorizeType, PaymentsCaptureType, PaymentsCompleteAuthorizeType, - PaymentsPostSessionTokensType, PaymentsPreProcessingType, PaymentsSyncType, - PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType, Response, - SdkSessionUpdateType, SetupMandateType, VerifyWebhookSourceType, + IncrementalAuthorizationType, PaymentsAuthorizeType, PaymentsCaptureType, + PaymentsCompleteAuthorizeType, PaymentsPostSessionTokensType, PaymentsPreProcessingType, + PaymentsSyncType, PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType, + Response, SdkSessionUpdateType, SetupMandateType, VerifyWebhookSourceType, }, webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, }; @@ -73,7 +73,8 @@ use masking::{ExposeInterface, Mask, Maskable, PeekInterface, Secret}; #[cfg(feature = "payouts")] use router_env::{instrument, tracing}; use transformers::{ - self as paypal, auth_headers, PaypalAuthResponse, PaypalMeta, PaypalWebhookEventType, + self as paypal, auth_headers, PaypalAuthResponse, PaypalIncrementalAuthResponse, PaypalMeta, + PaypalWebhookEventType, }; use crate::{ @@ -1105,6 +1106,123 @@ impl ConnectorIntegration for Paypal +{ + fn get_headers( + &self, + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_http_method(&self) -> Method { + Method::Post + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult { + let paypal_meta: PaypalMeta = to_connector_meta(req.request.connector_meta.clone())?; + let incremental_authorization_id = paypal_meta.incremental_authorization_id.ok_or( + errors::ConnectorError::RequestEncodingFailedWithReason( + "Missing incremental authorization id".to_string(), + ), + )?; + Ok(format!( + "{}v2/payments/authorizations/{}/reauthorize", + self.base_url(connectors), + incremental_authorization_id + )) + } + + fn get_request_body( + &self, + req: &PaymentsIncrementalAuthorizationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = connector_utils::convert_amount( + self.amount_converter, + MinorUnit::new(req.request.total_amount), + req.request.currency, + )?; + let connector_router_data = + paypal::PaypalRouterData::try_from((amount, None, None, None, req))?; + let connector_req = paypal::PaypalIncrementalAuthRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&IncrementalAuthorizationType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(IncrementalAuthorizationType::get_headers( + self, req, connectors, + )?) + .set_body(IncrementalAuthorizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsIncrementalAuthorizationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult< + RouterData< + IncrementalAuthorization, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, + >, + errors::ConnectorError, + > { + let response: PaypalIncrementalAuthResponse = res + .response + .parse_struct("Paypal IncrementalAuthResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + 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, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl api::PaymentsPreProcessing for Paypal {} impl ConnectorIntegration diff --git a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs index 8cabfcf466..3814694b08 100644 --- a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs @@ -19,7 +19,8 @@ use hyperswitch_domain_models::{ VerifyWebhookSource, }, router_request_types::{ - PaymentsAuthorizeData, PaymentsPostSessionTokensData, PaymentsSyncData, ResponseId, + CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsIncrementalAuthorizationData, + PaymentsPostSessionTokensData, PaymentsSyncData, ResponseId, VerifyWebhookSourceRequestData, }, router_response_types::{ @@ -28,8 +29,9 @@ use hyperswitch_domain_models::{ }, types::{ PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, - PaymentsPostSessionTokensRouterData, RefreshTokenRouterData, RefundsRouterData, - SdkSessionUpdateRouterData, SetupMandateRouterData, VerifyWebhookSourceRouterData, + PaymentsIncrementalAuthorizationRouterData, PaymentsPostSessionTokensRouterData, + RefreshTokenRouterData, RefundsRouterData, SdkSessionUpdateRouterData, + SetupMandateRouterData, VerifyWebhookSourceRouterData, }, }; #[cfg(feature = "payouts")] @@ -55,6 +57,28 @@ use crate::{ }, }; +trait GetRequestIncrementalAuthorization { + fn get_request_incremental_authorization(&self) -> Option; +} + +impl GetRequestIncrementalAuthorization for PaymentsAuthorizeData { + fn get_request_incremental_authorization(&self) -> Option { + Some(self.request_incremental_authorization) + } +} + +impl GetRequestIncrementalAuthorization for CompleteAuthorizeData { + fn get_request_incremental_authorization(&self) -> Option { + None + } +} + +impl GetRequestIncrementalAuthorization for PaymentsSyncData { + fn get_request_incremental_authorization(&self) -> Option { + None + } +} + #[derive(Debug, Serialize)] pub struct PaypalRouterData { pub amount: StringMajorUnit, @@ -237,7 +261,7 @@ pub struct PurchaseUnitRequest { items: Vec, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[derive(Default, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct Payee { merchant_id: Secret, } @@ -1425,6 +1449,119 @@ impl TryFrom> + for PaypalIncrementalAuthRequest +{ + type Error = error_stack::Report; + + fn try_from( + item: &PaypalRouterData<&PaymentsIncrementalAuthorizationRouterData>, + ) -> Result { + Ok(Self { + amount: OrderAmount { + currency_code: item.router_data.request.currency, + value: item.amount.clone(), + }, + }) + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PaypalIncrementalAuthResponse { + status: PaypalIncrementalStatus, + status_details: PaypalIncrementalAuthStatusDetails, + id: String, + invoice_id: String, + custom_id: String, + links: Vec, + amount: OrderAmount, + network_transaction_reference: PaypalNetworkTransactionReference, + expiration_time: String, + create_time: String, + update_time: String, + supplementary_data: PaypalSupplementaryData, + payee: Payee, + name: Option, + message: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PaypalIncrementalStatus { + CREATED, + CAPTURED, + DENIED, + PARTIALLYCAPTURED, + VOIDED, + PENDING, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PaypalNetworkTransactionReference { + id: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PaypalIncrementalAuthStatusDetails { + reason: PaypalStatusPendingReason, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PaypalStatusPendingReason { + PENDINGREVIEW, + DECLINEDBYRISKFRAUDFILTERS, +} + +impl From for common_enums::AuthorizationStatus { + fn from(item: PaypalIncrementalStatus) -> Self { + match item { + PaypalIncrementalStatus::CREATED + | PaypalIncrementalStatus::CAPTURED + | PaypalIncrementalStatus::PARTIALLYCAPTURED => Self::Success, + PaypalIncrementalStatus::PENDING => Self::Processing, + PaypalIncrementalStatus::DENIED | PaypalIncrementalStatus::VOIDED => Self::Failure, + } + } +} + +impl + TryFrom< + ResponseRouterData< + F, + PaypalIncrementalAuthResponse, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + PaypalIncrementalAuthResponse, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, + >, + ) -> Result { + let status = common_enums::AuthorizationStatus::from(item.response.status); + Ok(Self { + response: Ok(PaymentsResponseData::IncrementalAuthorizationResponse { + status, + error_code: None, + error_message: None, + connector_authorization_id: Some(item.response.id), + }), + ..item.data + }) + } +} + #[derive(Debug)] pub enum PaypalAuthType { TemporaryAuth, @@ -1747,6 +1884,7 @@ pub struct CardDetails { pub struct PaypalMeta { pub authorize_id: Option, pub capture_id: Option, + pub incremental_authorization_id: Option, pub psync_flow: PaypalPaymentIntent, pub next_action: Option, pub order_id: Option, @@ -1782,12 +1920,25 @@ fn get_id_based_on_intent( .ok_or_else(|| errors::ConnectorError::MissingConnectorTransactionID.into()) } -impl TryFrom> - for RouterData +fn extract_incremental_authorization_id(response: &PaypalOrdersResponse) -> Option { + for unit in &response.purchase_units { + if let Some(authorizations) = &unit.payments.authorizations { + if let Some(first_auth) = authorizations.first() { + return Some(first_auth.id.clone()); + } + } + } + None +} + +impl TryFrom> + for RouterData +where + Req: GetRequestIncrementalAuthorization, { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData, ) -> Result { let purchase_units = item .response @@ -1801,6 +1952,7 @@ impl TryFrom TryFrom TryFrom let connector_meta = serde_json::json!(PaypalMeta { authorize_id: None, capture_id: None, + incremental_authorization_id: None, psync_flow: item.response.intent, next_action, order_id: None, @@ -2017,6 +2176,7 @@ impl let connector_meta = serde_json::json!(PaypalMeta { authorize_id: None, capture_id: None, + incremental_authorization_id: None, psync_flow: item.response.intent, next_action: None, order_id: None, @@ -2072,6 +2232,7 @@ impl let connector_meta = serde_json::json!(PaypalMeta { authorize_id: None, capture_id: None, + incremental_authorization_id: None, psync_flow: item.response.intent, next_action, order_id: Some(item.response.id.clone()), @@ -2144,6 +2305,7 @@ impl let connector_meta = serde_json::json!(PaypalMeta { authorize_id: None, capture_id: None, + incremental_authorization_id: None, psync_flow: PaypalPaymentIntent::Authenticate, // when there is no capture or auth id present next_action: None, order_id: None, @@ -2550,6 +2712,7 @@ impl TryFrom> connector_metadata: Some(serde_json::json!(PaypalMeta { authorize_id: connector_payment_id.authorize_id, capture_id: Some(item.response.id.clone()), + incremental_authorization_id: None, psync_flow: PaypalPaymentIntent::Capture, next_action: None, order_id: None, diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 38e6312402..bdf3ee5604 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -1086,7 +1086,6 @@ default_imp_for_incremental_authorization!( connectors::Payload, connectors::Payme, connectors::Payone, - connectors::Paypal, connectors::Paystack, connectors::Payu, connectors::Placetopay, diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 76211df58c..2d2b412408 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -155,6 +155,7 @@ pub struct PaymentsIncrementalAuthorizationData { pub currency: storage_enums::Currency, pub reason: Option, pub connector_transaction_id: String, + pub connector_meta: Option, } #[derive(Debug, Clone, Default)] diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs index 4162eb2909..70d1bc2c1b 100644 --- a/crates/hyperswitch_interfaces/src/api.rs +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -722,7 +722,7 @@ pub trait ConnectorTransactionId: ConnectorCommon + Sync { /// fn connector_transaction_id fn connector_transaction_id( &self, - payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, + payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, ) -> Result, ApiErrorResponse> { Ok(payment_attempt .get_connector_payment_id() diff --git a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs index 998df9ec61..c40c65ac5f 100644 --- a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs +++ b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs @@ -793,7 +793,7 @@ impl api::ConnectorTransactionId for ConnectorEnum { /// A `Result` containing an optional transaction ID or an ApiErrorResponse fn connector_transaction_id( &self, - payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, + payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, ) -> Result, ApiErrorResponse> { match self { Self::Old(connector) => connector.connector_transaction_id(payment_attempt), diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 88d2cc4032..1df3b6ef29 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -478,7 +478,7 @@ pub async fn construct_payment_router_data_for_capture<'a>( currency: payment_data.payment_intent.amount_details.currency, connector_transaction_id: connector .connector - .connector_transaction_id(payment_data.payment_attempt.clone())? + .connector_transaction_id(&payment_data.payment_attempt)? .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module minor_payment_amount: amount, @@ -3875,6 +3875,43 @@ impl TryFrom> for types::PaymentsSyncData } } +#[cfg(feature = "v1")] +impl TryFrom> + for types::PaymentsIncrementalAuthorizationData +{ + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + let payment_data = additional_data.payment_data; + let payment_attempt = &payment_data.payment_attempt; + let connector = api::ConnectorData::get_connector_by_name( + &additional_data.state.conf.connectors, + &additional_data.connector_name, + api::GetToken::Connector, + payment_attempt.merchant_connector_id.clone(), + )?; + let incremental_details = payment_data + .incremental_authorization_details + .as_ref() + .ok_or( + report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("missing incremental_authorization_details in payment_data"), + )?; + Ok(Self { + total_amount: incremental_details.total_amount.get_amount_as_i64(), + additional_amount: incremental_details.additional_amount.get_amount_as_i64(), + reason: incremental_details.reason.clone(), + currency: payment_data.currency, + connector_transaction_id: connector + .connector + .connector_transaction_id(payment_attempt)? + .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, + connector_meta: payment_attempt.connector_metadata.clone(), + }) + } +} + +#[cfg(feature = "v2")] impl TryFrom> for types::PaymentsIncrementalAuthorizationData { @@ -3888,33 +3925,26 @@ impl TryFrom> api::GetToken::Connector, payment_data.payment_attempt.merchant_connector_id.clone(), )?; - let total_amount = payment_data + let incremental_details = payment_data .incremental_authorization_details - .clone() - .map(|details| details.total_amount) - .ok_or( - report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable("missing incremental_authorization_details in payment_data"), - )?; - let additional_amount = payment_data - .incremental_authorization_details - .clone() - .map(|details| details.additional_amount) + .as_ref() .ok_or( report!(errors::ApiErrorResponse::InternalServerError) .attach_printable("missing incremental_authorization_details in payment_data"), )?; Ok(Self { - total_amount: total_amount.get_amount_as_i64(), - additional_amount: additional_amount.get_amount_as_i64(), - reason: payment_data - .incremental_authorization_details - .and_then(|details| details.reason), + total_amount: incremental_details.total_amount.get_amount_as_i64(), + additional_amount: incremental_details.additional_amount.get_amount_as_i64(), + reason: incremental_details.reason.clone(), currency: payment_data.currency, connector_transaction_id: connector .connector - .connector_transaction_id(payment_data.payment_attempt.clone())? + .connector_transaction_id(&payment_data.payment_attempt)? .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, + connector_meta: payment_data + .payment_attempt + .connector_metadata + .map(|secret| secret.expose()), }) } } @@ -3947,7 +3977,7 @@ impl TryFrom> for types::PaymentsCaptureD currency: payment_data.currency, connector_transaction_id: connector .connector - .connector_transaction_id(payment_data.payment_attempt.clone())? + .connector_transaction_id(&payment_data.payment_attempt)? .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module minor_payment_amount: amount, @@ -4015,7 +4045,7 @@ impl TryFrom> for types::PaymentsCaptureD currency: payment_data.currency, connector_transaction_id: connector .connector - .connector_transaction_id(payment_data.payment_attempt.clone())? + .connector_transaction_id(&payment_data.payment_attempt)? .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module minor_payment_amount: amount, @@ -4092,7 +4122,7 @@ impl TryFrom> for types::PaymentsCancelDa currency: Some(payment_data.currency), connector_transaction_id: connector .connector - .connector_transaction_id(payment_data.payment_attempt.clone())? + .connector_transaction_id(&payment_data.payment_attempt)? .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, cancellation_reason: payment_data.payment_attempt.cancellation_reason, connector_meta: payment_data.payment_attempt.connector_metadata, @@ -4246,7 +4276,7 @@ impl TryFrom> for types::PaymentsUpdateMe .attach_printable("payment_intent.metadata not found")?, connector_transaction_id: connector .connector - .connector_transaction_id(payment_data.payment_attempt.clone())? + .connector_transaction_id(&payment_data.payment_attempt)? .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, }) } diff --git a/cypress-tests/cypress/e2e/configs/Payment/Paypal.js b/cypress-tests/cypress/e2e/configs/Payment/Paypal.js index 889bf51498..7399ae1c8a 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Paypal.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Paypal.js @@ -1,3 +1,5 @@ +import { getCustomExchange } from "./Modifiers"; + const successfulNo3DSCardDetails = { card_number: "4012000033330026", card_exp_month: "01", @@ -165,7 +167,7 @@ export const connectorDetails = { }, }, }, - Capture: { + Capture: getCustomExchange({ Request: { amount_to_capture: 6000, }, @@ -178,7 +180,16 @@ export const connectorDetails = { amount_received: 6000, }, }, - }, + ResponseCustom: { + status: 422, + body: { + error: { + code: "IR_06", + message: "amount_to_capture is greater than amount", // Incremental authorization is not allowed within 4 days of the initial authorization. Since the capture amount (8000) exceeds the authorized amount, this request fails. + }, + }, + }, + }), PartialCapture: { Request: { amount_to_capture: 2000, @@ -254,6 +265,29 @@ export const connectorDetails = { }, }, }, + IncrementalAuth: { + Request: { + amount: 8000, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + amount: 8000, + amount_capturable: 6000, // Incremental Authorization can be done atleast 4 days after the authorization in case of Paypal + amount_received: null, + incremental_authorizations: [ + { + amount: 8000, + previously_authorized_amount: 6000, + status: "failure", + error_code: "REAUTHORIZATION_TOO_SOON", + error_message: "REAUTHORIZATION_TOO_SOON", + }, + ], + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index cab756575c..2504258328 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -392,6 +392,7 @@ export const CONNECTOR_LISTS = { MANDATES_USING_NTID_PROXY: ["cybersource"], INCREMENTAL_AUTH: [ // "cybersource" // issues with MULTIPLE_CONNECTORS handling + "paypal", ], // Add more inclusion lists }, diff --git a/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js index dd0a2ad4da..1dfe48c338 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js @@ -88,6 +88,7 @@ describe("[Payment] Incremental Auth", () => { const newData = { ...data, Request: { amount_to_capture: data.Request.amount_to_capture + 2000 }, + Response: data.ResponseCustom || data.Response, }; cy.captureCallTest(fixtures.captureBody, newData, globalState); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 3f15439a35..a600bc6890 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -3967,14 +3967,38 @@ Cypress.Commands.add("incrementalAuth", (globalState, data) => { .to.have.property("amount") .to.be.a("number") .to.equal(resData.body.amount).and.not.be.null; - expect( - response.body.incremental_authorizations[key], - "error_code" - ).to.have.property("error_code").to.be.null; - expect( - response.body.incremental_authorizations[key], - "error_message" - ).to.have.property("error_message").to.be.null; + if ( + response.body.incremental_authorizations[key].status === "failure" + ) { + expect(response.body.incremental_authorizations[key], "error_code") + .to.have.property("error_code") + .to.be.equal( + resData.body.incremental_authorizations[key].error_code + ); + expect( + response.body.incremental_authorizations[key], + "error_message" + ) + .to.have.property("error_message") + .to.be.equal( + resData.body.incremental_authorizations[key].error_message + ); + expect(response.body.incremental_authorizations[key], "status") + .to.have.property("status") + .to.equal("failure"); + } else { + expect( + response.body.incremental_authorizations[key], + "error_code" + ).to.have.property("error_code").to.be.null; + expect( + response.body.incremental_authorizations[key], + "error_message" + ).to.have.property("error_message").to.be.null; + expect(response.body.incremental_authorizations[key], "status") + .to.have.property("status") + .to.equal("success"); + } expect( response.body.incremental_authorizations[key], "previously_authorized_amount" @@ -3985,9 +4009,6 @@ Cypress.Commands.add("incrementalAuth", (globalState, data) => { response.body.incremental_authorizations[key] .previously_authorized_amount ).and.not.be.null; - expect(response.body.incremental_authorizations[key], "status") - .to.have.property("status") - .to.equal("success"); } } });