diff --git a/api-reference/docs.json b/api-reference/docs.json index c0418b11231..fa64c38d547 100644 --- a/api-reference/docs.json +++ b/api-reference/docs.json @@ -45,6 +45,7 @@ "v1/payments/payments--cancel-post-capture", "v1/payments/payments--capture", "v1/payments/payments--incremental-authorization", + "v1/payments/payments--extend-authorization", "v1/payments/payments--session-token", "v1/payments/payments-link--retrieve", "v1/payments/payments--list", diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index 58b315e4113..329783c0782 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -1144,6 +1144,62 @@ ] } }, + "/payments/{payment_id}/extend_authorization": { + "post": { + "tags": [ + "Payments" + ], + "summary": "Payments - Extended Authorization", + "description": "Extended authorization is available for payments currently in the `requires_capture` status\nCall this endpoint to increase the authorization validity period", + "operationId": "Extend authorization period for a Payment", + "parameters": [ + { + "name": "payment_id", + "in": "path", + "description": "The identifier for payment", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsExtendAuthorizationRequest" + }, + "examples": { + "Increase the authorization validity period with minimal fields": { + "value": {} + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Extended authorization for the payment" + }, + "400": { + "description": "Missing mandatory fields", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericErrorResponseOpenApi" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/payments/list": { "get": { "tags": [ @@ -24563,6 +24619,10 @@ } } }, + "PaymentsExtendAuthorizationRequest": { + "type": "object", + "description": "Request to extend the authorization period for a payment" + }, "PaymentsExternalAuthenticationRequest": { "type": "object", "required": [ diff --git a/api-reference/v1/payments/payments--extend-authorization.mdx b/api-reference/v1/payments/payments--extend-authorization.mdx new file mode 100644 index 00000000000..c00a21cb407 --- /dev/null +++ b/api-reference/v1/payments/payments--extend-authorization.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /payments/{payment_id}/extend_authorization +--- \ No newline at end of file diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index ad107225177..df87f84b53b 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -31,11 +31,12 @@ use crate::{ PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelPostCaptureRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, - PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, - PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, - PaymentsManualUpdateResponse, PaymentsPostSessionTokensRequest, - PaymentsPostSessionTokensResponse, PaymentsRejectRequest, PaymentsRetrieveRequest, - PaymentsStartRequest, PaymentsUpdateMetadataRequest, PaymentsUpdateMetadataResponse, + PaymentsExtendAuthorizationRequest, PaymentsExternalAuthenticationRequest, + PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, + PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, + PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRejectRequest, + PaymentsRetrieveRequest, PaymentsStartRequest, PaymentsUpdateMetadataRequest, + PaymentsUpdateMetadataResponse, }, }; @@ -143,6 +144,14 @@ impl ApiEventMetric for PaymentsCancelPostCaptureRequest { }) } } +#[cfg(feature = "v1")] +impl ApiEventMetric for PaymentsExtendAuthorizationRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} #[cfg(feature = "v1")] impl ApiEventMetric for PaymentsApproveRequest { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 6a51e5c5d77..a8cc12a7677 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -8449,6 +8449,14 @@ pub struct PaymentsCancelPostCaptureRequest { pub cancellation_reason: Option, } +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +/// Request to extend the authorization period for a payment +pub struct PaymentsExtendAuthorizationRequest { + /// The identifier for the payment + #[serde(skip)] + pub payment_id: id_type::PaymentId, +} + #[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] pub struct PaymentsIncrementalAuthorizationRequest { /// The identifier for the payment diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index 23212cb69f1..3fdeaa44f76 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -41,6 +41,8 @@ pub enum IncomingWebhookEvent { MandateActive, MandateRevoked, EndpointVerification, + ExtendedAuthorization, + ExtendAuthorizationFailed, ExternalAuthenticationARes, FrmApproved, FrmRejected, @@ -265,7 +267,9 @@ impl From for WebhookFlow { | IncomingWebhookEvent::PaymentIntentAuthorizationFailure | IncomingWebhookEvent::PaymentIntentCaptureSuccess | IncomingWebhookEvent::PaymentIntentCaptureFailure - | IncomingWebhookEvent::PaymentIntentExpired => Self::Payment, + | IncomingWebhookEvent::PaymentIntentExpired + | IncomingWebhookEvent::ExtendedAuthorization + | IncomingWebhookEvent::ExtendAuthorizationFailed => Self::Payment, IncomingWebhookEvent::EventNotSupported => Self::ReturnResponse, IncomingWebhookEvent::RefundSuccess | IncomingWebhookEvent::RefundFailure => { Self::Refund diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index a42a266114f..b63784a9e44 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -427,6 +427,30 @@ pub enum AuthorizationStatus { Unresolved, } +#[derive( + Default, + Clone, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, + Hash, +)] +#[router_derive::diesel_enum(storage_type = "text")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum ExtendedAuthorizationStatus { + Success, + Failure, + // Processing state is before calling connector + #[default] + Processing, +} + #[derive( Clone, Debug, diff --git a/crates/hyperswitch_connectors/src/connectors/adyen.rs b/crates/hyperswitch_connectors/src/connectors/adyen.rs index bb4cdbda265..27ad2c2e145 100644 --- a/crates/hyperswitch_connectors/src/connectors/adyen.rs +++ b/crates/hyperswitch_connectors/src/connectors/adyen.rs @@ -21,8 +21,8 @@ use hyperswitch_domain_models::{ router_flow_types::{ access_token_auth::AccessTokenAuth, payments::{ - Authorize, Capture, PSync, PaymentMethodToken, PreProcessing, Session, SetupMandate, - Void, + Authorize, Capture, ExtendAuthorization, PSync, PaymentMethodToken, PreProcessing, + Session, SetupMandate, Void, }, refunds::{Execute, RSync}, Accept, Defend, Evidence, GiftCardBalanceCheck, Retrieve, Upload, @@ -30,9 +30,10 @@ use hyperswitch_domain_models::{ router_request_types::{ AcceptDisputeRequestData, AccessTokenRequestData, DefendDisputeRequestData, GiftCardBalanceCheckRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSessionData, - PaymentsSyncData, RefundsData, RetrieveFileRequestData, SetupMandateRequestData, - SubmitEvidenceRequestData, SyncRequestType, UploadFileRequestData, + PaymentsCancelData, PaymentsCaptureData, PaymentsExtendAuthorizationData, + PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, RefundsData, + RetrieveFileRequestData, SetupMandateRequestData, SubmitEvidenceRequestData, + SyncRequestType, UploadFileRequestData, }, router_response_types::{ AcceptDisputeResponse, ConnectorInfo, DefendDisputeResponse, @@ -42,8 +43,9 @@ use hyperswitch_domain_models::{ }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsGiftCardBalanceCheckRouterData, PaymentsPreProcessingRouterData, - PaymentsSyncRouterData, RefundsRouterData, SetupMandateRouterData, + PaymentsExtendAuthorizationRouterData, PaymentsGiftCardBalanceCheckRouterData, + PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefundsRouterData, + SetupMandateRouterData, }, }; #[cfg(feature = "payouts")] @@ -69,9 +71,10 @@ use hyperswitch_interfaces::{ disputes, errors, events::connector_api_logs::ConnectorEvent, types::{ - AcceptDisputeType, DefendDisputeType, PaymentsAuthorizeType, PaymentsCaptureType, - PaymentsGiftCardBalanceCheckType, PaymentsPreProcessingType, PaymentsSyncType, - PaymentsVoidType, RefundExecuteType, Response, SetupMandateType, SubmitEvidenceType, + AcceptDisputeType, DefendDisputeType, ExtendedAuthorizationType, PaymentsAuthorizeType, + PaymentsCaptureType, PaymentsGiftCardBalanceCheckType, PaymentsPreProcessingType, + PaymentsSyncType, PaymentsVoidType, RefundExecuteType, Response, SetupMandateType, + SubmitEvidenceType, }, webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails}, }; @@ -1581,6 +1584,111 @@ impl ConnectorIntegration for A } } +impl api::PaymentExtendAuthorization for Adyen {} +impl + ConnectorIntegration + for Adyen +{ + fn get_headers( + &self, + req: &PaymentsExtendAuthorizationRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.common_get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsExtendAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult { + let id = req.request.connector_transaction_id.as_str(); + let endpoint = build_env_specific_endpoint( + self.base_url(connectors), + req.test_mode, + &req.connector_meta_data, + )?; + Ok(format!( + "{endpoint}{ADYEN_API_VERSION}/payments/{id}/amountUpdates" + )) + } + + fn get_request_body( + &self, + req: &PaymentsExtendAuthorizationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = adyen::AdyenRouterData::try_from((amount, req))?; + let connector_req = + adyen::AdyenExtendAuthorizationRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsExtendAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&ExtendedAuthorizationType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(ExtendedAuthorizationType::get_headers( + self, req, connectors, + )?) + .set_body(ExtendedAuthorizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsExtendAuthorizationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: adyen::AdyenExtendAuthorizationResponse = res + .response + .parse_struct("Adyen AdyenExtendAuthorizationResponse") + .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) + } +} + #[cfg(feature = "payouts")] impl ConnectorIntegration for Adyen { fn get_url( @@ -1925,7 +2033,7 @@ impl IncomingWebhook for Adyen { let notif = get_webhook_object_from_body(request.body) .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; // for capture_event, original_reference field will have the authorized payment's PSP reference - if adyen::is_capture_or_cancel_event(¬if.event_code) { + if adyen::is_capture_or_cancel_or_adjust_event(¬if.event_code) { return Ok(api_models::webhooks::ObjectReferenceId::PaymentId( api_models::payments::PaymentIdType::ConnectorTransactionId( notif diff --git a/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs index e455882151a..674c09c35b8 100644 --- a/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs @@ -25,12 +25,13 @@ use hyperswitch_domain_models::{ NetworkTokenData, PayLaterData, PaymentMethodData, VoucherData, WalletData, }, router_data::{ - ConnectorAuthType, ErrorResponse, PaymentMethodBalance, PaymentMethodToken, RouterData, + ConnectorAuthType, ConnectorResponseData, ErrorResponse, ExtendedAuthorizationResponseData, + PaymentMethodBalance, PaymentMethodToken, RouterData, }, router_flow_types::GiftCardBalanceCheck, router_request_types::{ - GiftCardBalanceCheckRequestData, PaymentsPreProcessingData, ResponseId, - SubmitEvidenceRequestData, + GiftCardBalanceCheckRequestData, PaymentsExtendAuthorizationData, + PaymentsPreProcessingData, ResponseId, SubmitEvidenceRequestData, }, router_response_types::{ AcceptDisputeResponse, DefendDisputeResponse, GiftCardBalanceCheckResponseData, @@ -39,7 +40,8 @@ use hyperswitch_domain_models::{ }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsGiftCardBalanceCheckRouterData, PaymentsPreProcessingRouterData, RefundsRouterData, + PaymentsExtendAuthorizationRouterData, PaymentsGiftCardBalanceCheckRouterData, + PaymentsPreProcessingRouterData, RefundsRouterData, }, }; #[cfg(feature = "payouts")] @@ -425,12 +427,15 @@ impl ForeignTryFrom<(bool, AdyenWebhookStatus)> for storage_enums::AttemptStatus (is_manual_capture, adyen_webhook_status): (bool, AdyenWebhookStatus), ) -> Result { match adyen_webhook_status { - AdyenWebhookStatus::Authorised => match is_manual_capture { - true => Ok(Self::Authorized), - // In case of Automatic capture Authorized is the final status of the payment - false => Ok(Self::Charged), - }, - AdyenWebhookStatus::AuthorisationFailed => Ok(Self::Failure), + AdyenWebhookStatus::Authorised | AdyenWebhookStatus::AdjustedAuthorization => { + match is_manual_capture { + true => Ok(Self::Authorized), + // In case of Automatic capture Authorized is the final status of the payment + false => Ok(Self::Charged), + } + } + AdyenWebhookStatus::AuthorisationFailed + | AdyenWebhookStatus::AdjustAuthorizationFailed => Ok(Self::Failure), AdyenWebhookStatus::Cancelled => Ok(Self::Voided), AdyenWebhookStatus::CancelFailed => Ok(Self::VoidFailed), AdyenWebhookStatus::Captured => Ok(Self::Charged), @@ -522,6 +527,8 @@ pub enum AdyenWebhookStatus { Reversed, UnexpectedEvent, Expired, + AdjustedAuthorization, + AdjustAuthorizationFailed, } //Creating custom struct which can be consumed in Psync Handler triggered from Webhooks @@ -3994,20 +4001,35 @@ impl } } +fn build_connector_response(status: &AdyenWebhookStatus) -> Option { + let extended_authentication_applied = match status { + AdyenWebhookStatus::AdjustedAuthorization => { + Some(common_types::primitive_wrappers::ExtendedAuthorizationAppliedBool::from(true)) + } + AdyenWebhookStatus::AdjustAuthorizationFailed => { + Some(common_types::primitive_wrappers::ExtendedAuthorizationAppliedBool::from(false)) + } + _ => None, + }; + + let extend_authorization_response = ExtendedAuthorizationResponseData { + extended_authentication_applied, + capture_before: None, + }; + + Some(ConnectorResponseData::new( + None, + None, + Some(extend_authorization_response), + )) +} + pub fn get_adyen_response( response: AdyenResponse, is_capture_manual: bool, status_code: u16, pmt: Option, -) -> CustomResult< - ( - storage_enums::AttemptStatus, - Option, - PaymentsResponseData, - Option, - ), - errors::ConnectorError, -> { +) -> CustomResult { let status = get_adyen_payment_status(is_capture_manual, response.result_code, pmt); let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() @@ -4091,7 +4113,21 @@ pub fn get_adyen_response( let txn_amount = response.amount.map(|amount| amount.value); - Ok((status, error, payments_response_data, txn_amount)) + Ok(AdyenPaymentsResponseData { + status, + error, + payments_response_data, + txn_amount, + connector_response: None, + }) +} + +pub struct AdyenPaymentsResponseData { + pub status: storage_enums::AttemptStatus, + pub error: Option, + pub payments_response_data: PaymentsResponseData, + pub txn_amount: Option, + pub connector_response: Option, } pub fn get_webhook_response( @@ -4099,15 +4135,7 @@ pub fn get_webhook_response( is_capture_manual: bool, is_multiple_capture_psync_flow: bool, status_code: u16, -) -> CustomResult< - ( - storage_enums::AttemptStatus, - Option, - PaymentsResponseData, - Option, - ), - errors::ConnectorError, -> { +) -> CustomResult { let status = storage_enums::AttemptStatus::foreign_try_from(( is_capture_manual, response.status.clone(), @@ -4150,18 +4178,20 @@ pub fn get_webhook_response( }; let txn_amount = response.amount.as_ref().map(|amount| amount.value); + let connector_response = build_connector_response(&response.status); if is_multiple_capture_psync_flow { let capture_sync_response_list = utils::construct_captures_response_hashmap(vec![response.clone()])?; - Ok(( + Ok(AdyenPaymentsResponseData { status, error, - PaymentsResponseData::MultipleCaptureResponse { + payments_response_data: PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, }, txn_amount, - )) + connector_response, + }) } else { let mandate_reference = response .recurring_detail_reference @@ -4187,7 +4217,13 @@ pub fn get_webhook_response( charges: None, }; - Ok((status, error, payments_response_data, txn_amount)) + Ok(AdyenPaymentsResponseData { + status, + error, + payments_response_data, + txn_amount, + connector_response, + }) } } @@ -4196,15 +4232,7 @@ pub fn get_redirection_response( is_manual_capture: bool, status_code: u16, pmt: Option, -) -> CustomResult< - ( - storage_enums::AttemptStatus, - Option, - PaymentsResponseData, - Option, - ), - errors::ConnectorError, -> { +) -> CustomResult { let status = get_adyen_payment_status(is_manual_capture, response.result_code.clone(), pmt); let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() @@ -4291,7 +4319,13 @@ pub fn get_redirection_response( let txn_amount = response.amount.map(|amount| amount.value); - Ok((status, error, payments_response_data, txn_amount)) + Ok(AdyenPaymentsResponseData { + status, + error, + payments_response_data, + txn_amount, + connector_response: None, + }) } pub fn get_present_to_shopper_response( @@ -4299,15 +4333,7 @@ pub fn get_present_to_shopper_response( is_manual_capture: bool, status_code: u16, pmt: Option, -) -> CustomResult< - ( - storage_enums::AttemptStatus, - Option, - PaymentsResponseData, - Option, - ), - errors::ConnectorError, -> { +) -> CustomResult { let status = get_adyen_payment_status(is_manual_capture, response.result_code.clone(), pmt); let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() @@ -4363,7 +4389,13 @@ pub fn get_present_to_shopper_response( }; let txn_amount = response.amount.map(|amount| amount.value); - Ok((status, error, payments_response_data, txn_amount)) + Ok(AdyenPaymentsResponseData { + status, + error, + payments_response_data, + txn_amount, + connector_response: None, + }) } pub fn get_qr_code_response( @@ -4371,15 +4403,7 @@ pub fn get_qr_code_response( is_manual_capture: bool, status_code: u16, pmt: Option, -) -> CustomResult< - ( - storage_enums::AttemptStatus, - Option, - PaymentsResponseData, - Option, - ), - errors::ConnectorError, -> { +) -> CustomResult { let status = get_adyen_payment_status(is_manual_capture, response.result_code.clone(), pmt); let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() @@ -4435,7 +4459,13 @@ pub fn get_qr_code_response( let txn_amount = response.amount.map(|amount| amount.value); - Ok((status, error, payments_response_data, txn_amount)) + Ok(AdyenPaymentsResponseData { + status, + error, + payments_response_data, + txn_amount, + connector_response: None, + }) } pub fn get_redirection_error_response( @@ -4443,15 +4473,7 @@ pub fn get_redirection_error_response( is_manual_capture: bool, status_code: u16, pmt: Option, -) -> CustomResult< - ( - storage_enums::AttemptStatus, - Option, - PaymentsResponseData, - Option, - ), - errors::ConnectorError, -> { +) -> CustomResult { let status = get_adyen_payment_status(is_manual_capture, response.result_code, pmt); let error = { let (network_decline_code, network_error_message) = response @@ -4508,7 +4530,13 @@ pub fn get_redirection_error_response( charges: None, }; - Ok((status, error, payments_response_data, None)) + Ok(AdyenPaymentsResponseData { + status, + error, + payments_response_data, + txn_amount: None, + connector_response: None, + }) } pub fn get_qr_metadata( @@ -4765,7 +4793,7 @@ impl ), ) -> Result { let is_manual_capture = is_manual_capture(capture_method); - let (status, error, payment_response_data, amount) = match item.response { + let adyen_payments_response_data = match item.response { AdyenPaymentResponse::Response(response) => { get_adyen_response(*response, is_manual_capture, item.http_code, pmt)? } @@ -4789,17 +4817,23 @@ impl )?, }; - let minor_amount_captured = match status { + let minor_amount_captured = match adyen_payments_response_data.status { enums::AttemptStatus::Charged | enums::AttemptStatus::PartialCharged - | enums::AttemptStatus::PartialChargedAndChargeable => amount, + | enums::AttemptStatus::PartialChargedAndChargeable => { + adyen_payments_response_data.txn_amount + } _ => None, }; Ok(Self { - status, + status: adyen_payments_response_data.status, amount_captured: minor_amount_captured.map(|amount| amount.get_amount_as_i64()), - response: error.map_or_else(|| Ok(payment_response_data), Err), + response: adyen_payments_response_data.error.map_or_else( + || Ok(adyen_payments_response_data.payments_response_data), + Err, + ), + connector_response: adyen_payments_response_data.connector_response, minor_amount_captured, ..item.data }) @@ -4814,6 +4848,65 @@ pub struct AdyenCaptureRequest { reference: String, } +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdyenExtendAuthorizationRequest { + merchant_account: Secret, + amount: Amount, +} + +impl TryFrom<&AdyenRouterData<&PaymentsExtendAuthorizationRouterData>> + for AdyenExtendAuthorizationRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &AdyenRouterData<&PaymentsExtendAuthorizationRouterData>, + ) -> Result { + let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?; + let amount = Amount { + currency: item.router_data.request.currency, + value: item.amount, + }; + Ok(Self { + merchant_account: auth_type.merchant_account, + amount, + }) + } +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdyenExtendAuthorizationResponse { + merchant_account: Secret, + payment_psp_reference: Option, + psp_reference: Option, + amount: Amount, +} + +impl + TryFrom< + ResponseRouterData< + F, + AdyenExtendAuthorizationResponse, + PaymentsExtendAuthorizationData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + AdyenExtendAuthorizationResponse, + PaymentsExtendAuthorizationData, + PaymentsResponseData, + >, + ) -> Result { + // Asynchronous authorization adjustment + Ok(Self { ..item.data }) + } +} + impl TryFrom<&AdyenRouterData<&PaymentsCaptureRouterData>> for AdyenCaptureRequest { type Error = Error; fn try_from(item: &AdyenRouterData<&PaymentsCaptureRouterData>) -> Result { @@ -5038,6 +5131,7 @@ pub struct AdyenAmountWH { #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] pub enum WebhookEventCode { Authorisation, + AuthorisationAdjustment, Refund, CancelOrRefund, Cancellation, @@ -5074,10 +5168,11 @@ pub fn is_transaction_event(event_code: &WebhookEventCode) -> bool { ) } -pub fn is_capture_or_cancel_event(event_code: &WebhookEventCode) -> bool { +pub fn is_capture_or_cancel_or_adjust_event(event_code: &WebhookEventCode) -> bool { matches!( event_code, - WebhookEventCode::Capture + WebhookEventCode::AuthorisationAdjustment + | WebhookEventCode::Capture | WebhookEventCode::CaptureFailed | WebhookEventCode::Cancellation ) @@ -5147,6 +5242,13 @@ pub(crate) fn get_adyen_webhook_event( api_models::webhooks::IncomingWebhookEvent::PaymentIntentCancelFailure } } + WebhookEventCode::AuthorisationAdjustment => { + if is_success_scenario(is_success) { + api_models::webhooks::IncomingWebhookEvent::ExtendedAuthorization + } else { + api_models::webhooks::IncomingWebhookEvent::ExtendAuthorizationFailed + } + } WebhookEventCode::RefundFailed | WebhookEventCode::RefundReversed => { api_models::webhooks::IncomingWebhookEvent::RefundFailure } @@ -5292,6 +5394,13 @@ impl From for AdyenWebhookResponse { AdyenWebhookStatus::CancelFailed } } + WebhookEventCode::AuthorisationAdjustment => { + if is_success_scenario(notif.success) { + AdyenWebhookStatus::AdjustedAuthorization + } else { + AdyenWebhookStatus::AdjustAuthorizationFailed + } + } WebhookEventCode::OfferClosed => AdyenWebhookStatus::Expired, WebhookEventCode::Capture => { if is_success_scenario(notif.success) { diff --git a/crates/hyperswitch_connectors/src/connectors/paypal.rs b/crates/hyperswitch_connectors/src/connectors/paypal.rs index b92c53c327b..944208f9405 100644 --- a/crates/hyperswitch_connectors/src/connectors/paypal.rs +++ b/crates/hyperswitch_connectors/src/connectors/paypal.rs @@ -20,8 +20,9 @@ use hyperswitch_domain_models::{ router_flow_types::{ access_token_auth::AccessTokenAuth, payments::{ - Authorize, Capture, IncrementalAuthorization, PSync, PaymentMethodToken, - PostSessionTokens, PreProcessing, SdkSessionUpdate, Session, SetupMandate, Void, + Authorize, Capture, ExtendAuthorization, IncrementalAuthorization, PSync, + PaymentMethodToken, PostSessionTokens, PreProcessing, SdkSessionUpdate, Session, + SetupMandate, Void, }, refunds::{Execute, RSync}, CompleteAuthorize, VerifyWebhookSource, @@ -29,9 +30,10 @@ use hyperswitch_domain_models::{ router_request_types::{ AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData, - PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, RefundsData, ResponseId, - SdkPaymentsSessionUpdateData, SetupMandateRequestData, VerifyWebhookSourceRequestData, + PaymentsExtendAuthorizationData, PaymentsIncrementalAuthorizationData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, RefundsData, ResponseId, SdkPaymentsSessionUpdateData, + SetupMandateRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, @@ -39,10 +41,11 @@ use hyperswitch_domain_models::{ }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsCompleteAuthorizeRouterData, PaymentsIncrementalAuthorizationRouterData, - PaymentsPostSessionTokensRouterData, PaymentsPreProcessingRouterData, - PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData, - SdkSessionUpdateRouterData, SetupMandateRouterData, VerifyWebhookSourceRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsExtendAuthorizationRouterData, + PaymentsIncrementalAuthorizationRouterData, PaymentsPostSessionTokensRouterData, + PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefreshTokenRouterData, + RefundSyncRouterData, RefundsRouterData, SdkSessionUpdateRouterData, + SetupMandateRouterData, VerifyWebhookSourceRouterData, }, }; #[cfg(feature = "payouts")] @@ -63,10 +66,11 @@ use hyperswitch_interfaces::{ disputes, errors, events::connector_api_logs::ConnectorEvent, types::{ - IncrementalAuthorizationType, PaymentsAuthorizeType, PaymentsCaptureType, - PaymentsCompleteAuthorizeType, PaymentsPostSessionTokensType, PaymentsPreProcessingType, - PaymentsSyncType, PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType, - Response, SdkSessionUpdateType, SetupMandateType, VerifyWebhookSourceType, + ExtendedAuthorizationType, IncrementalAuthorizationType, PaymentsAuthorizeType, + PaymentsCaptureType, PaymentsCompleteAuthorizeType, PaymentsPostSessionTokensType, + PaymentsPreProcessingType, PaymentsSyncType, PaymentsVoidType, RefreshTokenType, + RefundExecuteType, RefundSyncType, Response, SdkSessionUpdateType, SetupMandateType, + VerifyWebhookSourceType, }, webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, }; @@ -119,6 +123,7 @@ impl api::RefundSync for Paypal {} impl api::ConnectorVerifyWebhookSource for Paypal {} impl api::PaymentPostSessionTokens for Paypal {} impl api::PaymentSessionUpdate for Paypal {} +impl api::PaymentExtendAuthorization for Paypal {} impl api::Payouts for Paypal {} #[cfg(feature = "payouts")] @@ -1094,6 +1099,107 @@ impl ConnectorIntegration + for Paypal +{ + fn get_headers( + &self, + req: &PaymentsExtendAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsExtendAuthorizationRouterData, + 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 id".to_string(), + ), + )?; + Ok(format!( + "{}v2/payments/authorizations/{}/reauthorize", + self.base_url(connectors), + incremental_authorization_id + )) + } + + fn get_request_body( + &self, + req: &PaymentsExtendAuthorizationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = + paypal::PaypalRouterData::try_from((amount, None, None, None, req))?; + let connector_req = + paypal::PaypalExtendAuthorizationRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsExtendAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&ExtendedAuthorizationType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(ExtendedAuthorizationType::get_headers( + self, req, connectors, + )?) + .set_body(ExtendedAuthorizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsExtendAuthorizationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: paypal::PaypalExtendedAuthResponse = res + .response + .parse_struct("Paypal PaypalExtendedAuthResponse") + .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 PaymentIncrementalAuthorization for Paypal {} impl diff --git a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs index 23d06999d6d..870f3a5d94d 100644 --- a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs @@ -12,16 +12,19 @@ use hyperswitch_domain_models::{ BankDebitData, BankRedirectData, BankTransferData, CardRedirectData, GiftCardData, PayLaterData, PaymentMethodData, VoucherData, WalletData, }, - router_data::{AccessToken, ConnectorAuthType, RouterData}, + router_data::{ + AccessToken, ConnectorAuthType, ConnectorResponseData, ErrorResponse, + ExtendedAuthorizationResponseData, RouterData, + }, router_flow_types::{ payments::{Authorize, PostSessionTokens}, refunds::{Execute, RSync}, VerifyWebhookSource, }, router_request_types::{ - CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsIncrementalAuthorizationData, - PaymentsPostSessionTokensData, PaymentsSyncData, ResponseId, - VerifyWebhookSourceRequestData, + CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsExtendAuthorizationData, + PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData, PaymentsSyncData, + ResponseId, VerifyWebhookSourceRequestData, }, router_response_types::{ MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, @@ -29,9 +32,9 @@ use hyperswitch_domain_models::{ }, types::{ PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, - PaymentsIncrementalAuthorizationRouterData, PaymentsPostSessionTokensRouterData, - RefreshTokenRouterData, RefundsRouterData, SdkSessionUpdateRouterData, - SetupMandateRouterData, VerifyWebhookSourceRouterData, + PaymentsExtendAuthorizationRouterData, PaymentsIncrementalAuthorizationRouterData, + PaymentsPostSessionTokensRouterData, RefreshTokenRouterData, RefundsRouterData, + SdkSessionUpdateRouterData, SetupMandateRouterData, VerifyWebhookSourceRouterData, }, }; #[cfg(feature = "payouts")] @@ -51,9 +54,9 @@ use crate::{constants, types::PayoutsResponseRouterData}; use crate::{ types::{PaymentsCaptureResponseRouterData, RefundsResponseRouterData, ResponseRouterData}, utils::{ - self, missing_field_err, to_connector_meta, AccessTokenRequestInfo, AddressDetailsData, - CardData, PaymentsAuthorizeRequestData, PaymentsPostSessionTokensRequestData, - RouterData as OtherRouterData, + self, is_payment_failure, missing_field_err, to_connector_meta, AccessTokenRequestInfo, + AddressDetailsData, CardData, PaymentsAuthorizeRequestData, + PaymentsPostSessionTokensRequestData, RouterData as OtherRouterData, }, }; @@ -910,6 +913,26 @@ impl TryFrom<&PaypalRouterData<&SdkSessionUpdateRouterData>> for PaypalUpdateOrd } } +#[derive(Debug, Serialize)] +pub struct PaypalExtendAuthorizationRequest { + amount: OrderAmount, +} + +impl TryFrom<&PaypalRouterData<&PaymentsExtendAuthorizationRouterData>> + for PaypalExtendAuthorizationRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&PaymentsExtendAuthorizationRouterData>, + ) -> Result { + let amount = OrderAmount { + currency_code: item.router_data.request.currency, + value: item.amount.clone(), + }; + Ok(Self { amount }) + } +} + impl TryFrom<&PaypalRouterData<&PaymentsAuthorizeRouterData>> for PaypalPaymentsRequest { type Error = error_stack::Report; fn try_from( @@ -1486,6 +1509,18 @@ impl TryFrom<&PaypalRouterData<&PaymentsIncrementalAuthorizationRouterData>> } } +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct PaypalExtendedAuthResponse { + status: PaypalIncrementalStatus, + status_details: Option, + id: String, + links: Vec, + expiration_time: Option, + create_time: Option, + update_time: Option, +} + #[derive(Debug, Deserialize, Serialize)] pub struct PaypalIncrementalAuthResponse { status: PaypalIncrementalStatus, @@ -1505,7 +1540,7 @@ pub struct PaypalIncrementalAuthResponse { message: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum PaypalIncrementalStatus { CREATED, @@ -1523,10 +1558,10 @@ pub struct PaypalNetworkTransactionReference { #[derive(Debug, Deserialize, Serialize)] pub struct PaypalIncrementalAuthStatusDetails { - reason: PaypalStatusPendingReason, + reason: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, strum::Display)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum PaypalStatusPendingReason { PENDINGREVIEW, @@ -1545,6 +1580,34 @@ impl From for common_enums::AuthorizationStatus { } } +impl From for common_enums::AttemptStatus { + fn from(item: PaypalIncrementalStatus) -> Self { + match item { + PaypalIncrementalStatus::CREATED + | PaypalIncrementalStatus::CAPTURED + | PaypalIncrementalStatus::PARTIALLYCAPTURED => Self::Authorized, + PaypalIncrementalStatus::PENDING => Self::Pending, + PaypalIncrementalStatus::DENIED | PaypalIncrementalStatus::VOIDED => Self::Failure, + } + } +} + +fn is_extend_authorization_applied( + item: PaypalIncrementalStatus, +) -> Option { + match item { + PaypalIncrementalStatus::CREATED + | PaypalIncrementalStatus::CAPTURED + | PaypalIncrementalStatus::PARTIALLYCAPTURED => { + Some(common_types::primitive_wrappers::ExtendedAuthorizationAppliedBool::from(true)) + } + PaypalIncrementalStatus::PENDING => None, + PaypalIncrementalStatus::DENIED | PaypalIncrementalStatus::VOIDED => { + Some(common_types::primitive_wrappers::ExtendedAuthorizationAppliedBool::from(false)) + } + } +} + impl TryFrom< ResponseRouterData< @@ -1577,6 +1640,83 @@ impl } } +impl + TryFrom< + ResponseRouterData< + F, + PaypalExtendedAuthResponse, + PaymentsExtendAuthorizationData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + PaypalExtendedAuthResponse, + PaymentsExtendAuthorizationData, + PaymentsResponseData, + >, + ) -> Result { + let extend_authorization_response = ExtendedAuthorizationResponseData { + extended_authentication_applied: is_extend_authorization_applied( + item.response.status.clone(), + ), + capture_before: item.response.expiration_time, + }; + + let connector_response = Some(ConnectorResponseData::new( + None, + None, + Some(extend_authorization_response), + )); + + let status = common_enums::AttemptStatus::from(item.response.status.clone()); + let reason = item + .response + .status_details + .and_then(|status_details| status_details.reason.map(|reason| reason.to_string())); + + let response = if is_payment_failure(status) { + Err(ErrorResponse { + code: reason + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: reason + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + connector_metadata: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: None, + }) + }; + + Ok(Self { + status, + response, + connector_response, + ..item.data + }) + } +} + #[derive(Debug)] pub enum PaypalAuthType { TemporaryAuth, diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index f143073a03d..0b9176f4d5a 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -33,9 +33,9 @@ use hyperswitch_domain_models::{ mandate_revoke::MandateRevoke, payments::{ Approve, AuthorizeSessionToken, CalculateTax, CompleteAuthorize, - CreateConnectorCustomer, CreateOrder, GiftCardBalanceCheck, IncrementalAuthorization, - PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, Reject, - SdkSessionUpdate, UpdateMetadata, + CreateConnectorCustomer, CreateOrder, ExtendAuthorization, GiftCardBalanceCheck, + IncrementalAuthorization, PostCaptureVoid, PostProcessing, PostSessionTokens, + PreProcessing, Reject, SdkSessionUpdate, UpdateMetadata, }, subscriptions::{GetSubscriptionEstimate, GetSubscriptionPlanPrices, GetSubscriptionPlans}, webhooks::VerifyWebhookSource, @@ -61,12 +61,12 @@ use hyperswitch_domain_models::{ DefendDisputeRequestData, DisputeSyncData, ExternalVaultProxyPaymentsData, FetchDisputesRequestData, GiftCardBalanceCheckRequestData, MandateRevokeRequestData, PaymentsApproveData, PaymentsAuthenticateData, PaymentsCancelPostCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostAuthenticateData, - PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreAuthenticateData, - PaymentsPreProcessingData, PaymentsRejectData, PaymentsTaxCalculationData, - PaymentsUpdateMetadataData, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, - SubmitEvidenceRequestData, UploadFileRequestData, VaultRequestData, - VerifyWebhookSourceRequestData, + PaymentsExtendAuthorizationData, PaymentsIncrementalAuthorizationData, + PaymentsPostAuthenticateData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, + PaymentsPreAuthenticateData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RetrieveFileRequestData, + SdkPaymentsSessionUpdateData, SubmitEvidenceRequestData, UploadFileRequestData, + VaultRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ revenue_recovery::InvoiceRecordBackResponse, @@ -130,11 +130,12 @@ use hyperswitch_interfaces::{ files::{FileUpload, RetrieveFile, UploadFile}, payments::{ ConnectorCustomer, ExternalVaultProxyPaymentsCreateV1, PaymentApprove, - PaymentAuthorizeSessionToken, PaymentIncrementalAuthorization, PaymentPostCaptureVoid, - PaymentPostSessionTokens, PaymentReject, PaymentSessionUpdate, PaymentUpdateMetadata, - PaymentsAuthenticate, PaymentsCompleteAuthorize, PaymentsCreateOrder, - PaymentsGiftCardBalanceCheck, PaymentsPostAuthenticate, PaymentsPostProcessing, - PaymentsPreAuthenticate, PaymentsPreProcessing, TaxCalculation, + PaymentAuthorizeSessionToken, PaymentExtendAuthorization, + PaymentIncrementalAuthorization, PaymentPostCaptureVoid, PaymentPostSessionTokens, + PaymentReject, PaymentSessionUpdate, PaymentUpdateMetadata, PaymentsAuthenticate, + PaymentsCompleteAuthorize, PaymentsCreateOrder, PaymentsGiftCardBalanceCheck, + PaymentsPostAuthenticate, PaymentsPostProcessing, PaymentsPreAuthenticate, + PaymentsPreProcessing, TaxCalculation, }, revenue_recovery::RevenueRecovery, subscriptions::{ @@ -1458,6 +1459,153 @@ default_imp_for_incremental_authorization!( connectors::CtpMastercard ); +macro_rules! default_imp_for_extend_authorization { + ($($path:ident::$connector:ident),*) => { + $( impl PaymentExtendAuthorization for $path::$connector {} + impl + ConnectorIntegration< + ExtendAuthorization, + PaymentsExtendAuthorizationData, + PaymentsResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_extend_authorization!( + connectors::Aci, + connectors::Adyenplatform, + connectors::Affirm, + connectors::Airwallex, + connectors::Amazonpay, + connectors::Archipel, + connectors::Authipay, + connectors::Authorizedotnet, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Bankofamerica, + connectors::Barclaycard, + connectors::Bitpay, + connectors::Blackhawknetwork, + connectors::Calida, + connectors::Bluesnap, + connectors::Braintree, + connectors::Boku, + connectors::Breadpay, + connectors::Billwerk, + connectors::Cashtocode, + connectors::Celero, + connectors::Chargebee, + connectors::Checkbook, + connectors::Checkout, + connectors::Coinbase, + connectors::Coingate, + connectors::Cryptopay, + connectors::Custombilling, + connectors::Cybersource, + connectors::Datatrans, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Dwolla, + connectors::Ebanx, + connectors::Elavon, + connectors::Facilitapay, + connectors::Finix, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Forte, + connectors::Getnet, + connectors::Gigadat, + connectors::Helcim, + connectors::HyperswitchVault, + connectors::Hyperwallet, + connectors::Iatapay, + connectors::Inespay, + connectors::Itaubank, + connectors::Jpmorgan, + connectors::Juspaythreedsserver, + connectors::Katapult, + connectors::Klarna, + connectors::Loonio, + connectors::Paysafe, + connectors::Rapyd, + connectors::Razorpay, + connectors::Recurly, + connectors::Redsys, + connectors::Santander, + connectors::Shift4, + connectors::Sift, + connectors::Silverflow, + connectors::Signifyd, + connectors::Square, + connectors::Stax, + connectors::Stripe, + connectors::Stripebilling, + connectors::Taxjar, + connectors::Tesouro, + connectors::Mifinity, + connectors::Mollie, + connectors::Moneris, + connectors::Mpgs, + connectors::Multisafepay, + connectors::Netcetera, + connectors::Nomupay, + connectors::Noon, + connectors::Nordea, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Nuvei, + connectors::Opayo, + connectors::Opennode, + connectors::Nmi, + connectors::Paybox, + connectors::Payeezy, + connectors::Payload, + connectors::Payme, + connectors::Paystack, + connectors::Paytm, + connectors::Payu, + connectors::Peachpayments, + connectors::Phonepe, + connectors::Placetopay, + connectors::Plaid, + connectors::Payone, + connectors::Fiuu, + connectors::Flexiti, + connectors::Globalpay, + connectors::Globepay, + connectors::Gocardless, + connectors::Gpayments, + connectors::Hipay, + connectors::Wise, + connectors::Worldline, + connectors::Worldpay, + connectors::Worldpayvantiv, + connectors::Worldpayxml, + connectors::Wellsfargo, + connectors::Wellsfargopayout, + connectors::Xendit, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Riskified, + connectors::Threedsecureio, + connectors::Thunes, + connectors::Tokenex, + connectors::Tokenio, + connectors::Trustpay, + connectors::Trustpayments, + connectors::Tsys, + connectors::UnifiedAuthenticationService, + connectors::Deutschebank, + connectors::Vgs, + connectors::Volt, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard +); + macro_rules! default_imp_for_create_customer { ($($path:ident::$connector:ident),*) => { $( @@ -9383,6 +9531,15 @@ impl { } +#[cfg(feature = "dummy_connector")] +impl PaymentExtendAuthorization for connectors::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + ConnectorIntegration + for connectors::DummyConnector +{ +} + #[cfg(feature = "dummy_connector")] impl UasPreAuthentication for connectors::DummyConnector {} #[cfg(feature = "dummy_connector")] diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 5cbc7a8531c..d861913755a 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -18,10 +18,10 @@ use hyperswitch_domain_models::{ mandate_revoke::MandateRevoke, payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, CreateOrder, ExternalVaultProxy, GiftCardBalanceCheck, - IncrementalAuthorization, PSync, PaymentMethodToken, PostCaptureVoid, PostProcessing, - PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, - UpdateMetadata, Void, + CreateConnectorCustomer, CreateOrder, ExtendAuthorization, ExternalVaultProxy, + GiftCardBalanceCheck, IncrementalAuthorization, PSync, PaymentMethodToken, + PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, Reject, + SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, revenue_recovery::{ @@ -43,9 +43,9 @@ use hyperswitch_domain_models::{ ExternalVaultProxyPaymentsData, FetchDisputesRequestData, GiftCardBalanceCheckRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, - PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsCaptureData, PaymentsExtendAuthorizationData, PaymentsIncrementalAuthorizationData, + PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, + PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, UploadFileRequestData, VaultRequestData, VerifyWebhookSourceRequestData, @@ -106,11 +106,12 @@ use hyperswitch_interfaces::{ payments_v2::{ ConnectorCustomerV2, ExternalVaultProxyPaymentsCreate, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, - PaymentCreateOrderV2, PaymentIncrementalAuthorizationV2, PaymentPostCaptureVoidV2, - PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, - PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, - PaymentsCompleteAuthorizeV2, PaymentsGiftCardBalanceCheckV2, PaymentsPostProcessingV2, - PaymentsPreProcessingV2, TaxCalculationV2, + PaymentCreateOrderV2, PaymentExtendAuthorizationV2, PaymentIncrementalAuthorizationV2, + PaymentPostCaptureVoidV2, PaymentPostSessionTokensV2, PaymentRejectV2, + PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, + PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, + PaymentsGiftCardBalanceCheckV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + TaxCalculationV2, }, refunds_v2::{RefundExecuteV2, RefundSyncV2, RefundV2}, revenue_recovery_v2::{ @@ -144,6 +145,7 @@ macro_rules! default_imp_for_new_connector_integration_payment { impl PaymentSessionV2 for $path::$connector{} impl MandateSetupV2 for $path::$connector{} impl PaymentIncrementalAuthorizationV2 for $path::$connector{} + impl PaymentExtendAuthorizationV2 for $path::$connector{} impl PaymentsCompleteAuthorizeV2 for $path::$connector{} impl PaymentTokenV2 for $path::$connector{} impl ConnectorCustomerV2 for $path::$connector{} @@ -193,6 +195,14 @@ macro_rules! default_imp_for_new_connector_integration_payment { for $path::$connector{} impl ConnectorIntegrationV2< + ExtendAuthorization, + PaymentFlowData, + PaymentsExtendAuthorizationData, + PaymentsResponseData, + > + for $path::$connector{} + impl + ConnectorIntegrationV2< CompleteAuthorize, PaymentFlowData, CompleteAuthorizeData, diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs index 2b0e8b42046..086020b24fa 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs @@ -50,6 +50,9 @@ pub struct PreProcessing; #[derive(Debug, Clone)] pub struct IncrementalAuthorization; +#[derive(Debug, Clone)] +pub struct ExtendAuthorization; + #[derive(Debug, Clone)] pub struct PostProcessing; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 995fd9f2f38..0618b09bd31 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -886,6 +886,14 @@ pub struct PaymentsCancelPostCaptureData { pub minor_amount: Option, } +#[derive(Debug, Default, Clone, Serialize)] +pub struct PaymentsExtendAuthorizationData { + pub minor_amount: MinorUnit, + pub currency: storage_enums::Currency, + pub connector_transaction_id: String, + pub connector_meta: Option, +} + #[derive(Debug, Default, Clone)] pub struct PaymentsRejectData { pub amount: Option, diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index b170fcf6d98..e32f887b27e 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -13,10 +13,10 @@ use crate::{ AccessTokenAuth, AccessTokenAuthentication, Authenticate, AuthenticationConfirmation, Authorize, AuthorizeSessionToken, BillingConnectorInvoiceSync, BillingConnectorPaymentsSync, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, CreateOrder, Execute, ExternalVaultProxy, GiftCardBalanceCheck, - IncrementalAuthorization, PSync, PaymentMethodToken, PostAuthenticate, PostCaptureVoid, - PostSessionTokens, PreAuthenticate, PreProcessing, RSync, SdkSessionUpdate, Session, - SetupMandate, UpdateMetadata, VerifyWebhookSource, Void, + CreateConnectorCustomer, CreateOrder, Execute, ExtendAuthorization, ExternalVaultProxy, + GiftCardBalanceCheck, IncrementalAuthorization, PSync, PaymentMethodToken, + PostAuthenticate, PostCaptureVoid, PostSessionTokens, PreAuthenticate, PreProcessing, + RSync, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, VerifyWebhookSource, Void, }, router_request_types::{ revenue_recovery::{ @@ -37,11 +37,12 @@ use crate::{ ExternalVaultProxyPaymentsData, GiftCardBalanceCheckRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsAuthenticateData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostAuthenticateData, - PaymentsPostSessionTokensData, PaymentsPreAuthenticateData, PaymentsPreProcessingData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, - PaymentsUpdateMetadataData, RefundsData, SdkPaymentsSessionUpdateData, - SetupMandateRequestData, VaultRequestData, VerifyWebhookSourceRequestData, + PaymentsExtendAuthorizationData, PaymentsIncrementalAuthorizationData, + PaymentsPostAuthenticateData, PaymentsPostSessionTokensData, PaymentsPreAuthenticateData, + PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, + PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, VaultRequestData, + VerifyWebhookSourceRequestData, }, router_response_types::{ revenue_recovery::{ @@ -129,6 +130,8 @@ pub type PaymentsIncrementalAuthorizationRouterData = RouterData< PaymentsIncrementalAuthorizationData, PaymentsResponseData, >; +pub type PaymentsExtendAuthorizationRouterData = + RouterData; pub type SdkSessionUpdateRouterData = RouterData; diff --git a/crates/hyperswitch_interfaces/src/api/payments.rs b/crates/hyperswitch_interfaces/src/api/payments.rs index 2120d3cf063..511c6303038 100644 --- a/crates/hyperswitch_interfaces/src/api/payments.rs +++ b/crates/hyperswitch_interfaces/src/api/payments.rs @@ -4,9 +4,9 @@ use hyperswitch_domain_models::{ router_flow_types::{ payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, Reject, - SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, + CreateConnectorCustomer, ExtendAuthorization, IncrementalAuthorization, PSync, + PaymentMethodToken, PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, + Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, Authenticate, CreateOrder, ExternalVaultProxy, GiftCardBalanceCheck, PostAuthenticate, PreAuthenticate, @@ -16,11 +16,11 @@ use hyperswitch_domain_models::{ CreateOrderRequestData, ExternalVaultProxyPaymentsData, GiftCardBalanceCheckRequestData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthenticateData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, - PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostAuthenticateData, - PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreAuthenticateData, - PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, - PaymentsTaxCalculationData, PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, - SetupMandateRequestData, + PaymentsCaptureData, PaymentsExtendAuthorizationData, PaymentsIncrementalAuthorizationData, + PaymentsPostAuthenticateData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, + PaymentsPreAuthenticateData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, router_response_types::{ GiftCardBalanceCheckResponseData, PaymentsResponseData, TaxCalculationResponseData, @@ -50,6 +50,7 @@ pub trait Payment: + PaymentsPostProcessing + ConnectorCustomer + PaymentIncrementalAuthorization + + PaymentExtendAuthorization + PaymentSessionUpdate + PaymentPostSessionTokens + PaymentUpdateMetadata @@ -101,6 +102,16 @@ pub trait PaymentPostCaptureVoid: { } +/// trait PaymentExtendAuthorization +pub trait PaymentExtendAuthorization: + api::ConnectorIntegration< + ExtendAuthorization, + PaymentsExtendAuthorizationData, + PaymentsResponseData, +> +{ +} + /// trait PaymentApprove pub trait PaymentApprove: api::ConnectorIntegration diff --git a/crates/hyperswitch_interfaces/src/api/payments_v2.rs b/crates/hyperswitch_interfaces/src/api/payments_v2.rs index c0681670ebc..5bc7c2067a8 100644 --- a/crates/hyperswitch_interfaces/src/api/payments_v2.rs +++ b/crates/hyperswitch_interfaces/src/api/payments_v2.rs @@ -5,9 +5,10 @@ use hyperswitch_domain_models::{ router_flow_types::{ payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, CreateOrder, ExternalVaultProxy, IncrementalAuthorization, - PSync, PaymentMethodToken, PostCaptureVoid, PostProcessing, PostSessionTokens, - PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, + CreateConnectorCustomer, CreateOrder, ExtendAuthorization, ExternalVaultProxy, + IncrementalAuthorization, PSync, PaymentMethodToken, PostCaptureVoid, PostProcessing, + PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, + UpdateMetadata, Void, }, Authenticate, GiftCardBalanceCheck, PostAuthenticate, PreAuthenticate, }, @@ -16,11 +17,11 @@ use hyperswitch_domain_models::{ CreateOrderRequestData, ExternalVaultProxyPaymentsData, GiftCardBalanceCheckRequestData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthenticateData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, - PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostAuthenticateData, - PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreAuthenticateData, - PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, - PaymentsTaxCalculationData, PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, - SetupMandateRequestData, + PaymentsCaptureData, PaymentsExtendAuthorizationData, PaymentsIncrementalAuthorizationData, + PaymentsPostAuthenticateData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, + PaymentsPreAuthenticateData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, router_response_types::{ GiftCardBalanceCheckResponseData, PaymentsResponseData, TaxCalculationResponseData, @@ -112,6 +113,17 @@ pub trait PaymentIncrementalAuthorizationV2: { } +/// trait PaymentExtendAuthorizationV2 +pub trait PaymentExtendAuthorizationV2: + ConnectorIntegrationV2< + ExtendAuthorization, + PaymentFlowData, + PaymentsExtendAuthorizationData, + PaymentsResponseData, +> +{ +} + ///trait TaxCalculationV2 pub trait TaxCalculationV2: ConnectorIntegrationV2< @@ -292,6 +304,7 @@ pub trait PaymentV2: + PaymentsPostProcessingV2 + ConnectorCustomerV2 + PaymentIncrementalAuthorizationV2 + + PaymentExtendAuthorizationV2 + TaxCalculationV2 + PaymentSessionUpdateV2 + PaymentPostSessionTokensV2 diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index 6b9203a7363..82486f5801e 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -10,9 +10,10 @@ use hyperswitch_domain_models::{ mandate_revoke::MandateRevoke, payments::{ Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, InitPayment, PSync, - PaymentMethodToken, PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, - SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, + CreateConnectorCustomer, CreateOrder, ExtendAuthorization, IncrementalAuthorization, + InitPayment, PSync, PaymentMethodToken, PostCaptureVoid, PostProcessing, + PostSessionTokens, PreProcessing, SdkSessionUpdate, Session, SetupMandate, + UpdateMetadata, Void, }, refunds::{Execute, RSync}, revenue_recovery::{BillingConnectorPaymentsSync, InvoiceRecordBack}, @@ -50,10 +51,10 @@ use hyperswitch_domain_models::{ FetchDisputesRequestData, GiftCardBalanceCheckRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsAuthenticateData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostAuthenticateData, - PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreAuthenticateData, - PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, - PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, + PaymentsExtendAuthorizationData, PaymentsIncrementalAuthorizationData, + PaymentsPostAuthenticateData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, + PaymentsPreAuthenticateData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, UploadFileRequestData, VaultRequestData, VerifyWebhookSourceRequestData, @@ -191,6 +192,13 @@ pub type IncrementalAuthorizationType = dyn ConnectorIntegration< PaymentsResponseData, >; +/// Type alias for `ConnectorIntegration` +pub type ExtendedAuthorizationType = dyn ConnectorIntegration< + ExtendAuthorization, + PaymentsExtendAuthorizationData, + PaymentsResponseData, +>; + /// Type alias for ConnectorIntegration pub type GetSubscriptionPlanPricesType = dyn ConnectorIntegration< GetSubscriptionPlanPrices, diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 430fb7f0e3f..b12162cd475 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -79,6 +79,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_connector_session, routes::payments::payments_cancel, routes::payments::payments_cancel_post_capture, + routes::payments::payments_extend_authorization, routes::payments::payments_list, routes::payments::payments_incremental_authorization, routes::payment_link::payment_link_retrieve, @@ -556,6 +557,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplepayPaymentMethod, api_models::payments::PaymentsCancelRequest, api_models::payments::PaymentsCancelPostCaptureRequest, + api_models::payments::PaymentsExtendAuthorizationRequest, api_models::payments::PaymentListConstraints, api_models::payments::PaymentListResponse, api_models::payments::CashappQr, diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index dfa14ffb7b2..777d6b06229 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -921,6 +921,36 @@ pub async fn profile_payments_list() {} )] pub fn payments_incremental_authorization() {} +/// Payments - Extended Authorization +/// +/// Extended authorization is available for payments currently in the `requires_capture` status +/// Call this endpoint to increase the authorization validity period +#[utoipa::path( + post, + path = "/payments/{payment_id}/extend_authorization", + request_body ( + content = PaymentsExtendAuthorizationRequest, + examples( + ( + "Increase the authorization validity period with minimal fields" = ( + value = json!({}) + ) + ) + ) + ), + params( + ("payment_id" = String, Path, description = "The identifier for payment") + ), + responses( + (status = 200, description = "Extended authorization for the payment"), + (status = 400, description = "Missing mandatory fields", body = GenericErrorResponseOpenApi) + ), + tag = "Payments", + operation_id = "Extend authorization period for a Payment", + security(("api_key" = [])) +)] +pub fn payments_extend_authorization() {} + /// Payments - External 3DS Authentication /// /// External 3DS Authentication is performed and returns the AuthenticationResponse diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 7c953a53253..a48dbb819b8 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -84,8 +84,9 @@ use time; #[cfg(feature = "v1")] pub use self::operations::{ PaymentApprove, PaymentCancel, PaymentCancelPostCapture, PaymentCapture, PaymentConfirm, - PaymentCreate, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, - PaymentSession, PaymentSessionUpdate, PaymentStatus, PaymentUpdate, PaymentUpdateMetadata, + PaymentCreate, PaymentExtendAuthorization, PaymentIncrementalAuthorization, + PaymentPostSessionTokens, PaymentReject, PaymentSession, PaymentSessionUpdate, PaymentStatus, + PaymentUpdate, PaymentUpdateMetadata, }; use self::{ conditional_configs::perform_decision_management, @@ -8175,6 +8176,12 @@ where "PaymentSessionUpdate" => true, "PaymentPostSessionTokens" => true, "PaymentUpdateMetadata" => true, + "PaymentExtendAuthorization" => matches!( + payment_data.get_payment_intent().status, + storage_enums::IntentStatus::RequiresCapture + | storage_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture + | storage_enums::IntentStatus::PartiallyCapturedAndCapturable + ), "PaymentIncrementalAuthorization" => matches!( payment_data.get_payment_intent().status, storage_enums::IntentStatus::RequiresCapture diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 612f0e36176..4772ebfcb52 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -4,6 +4,7 @@ pub mod cancel_flow; pub mod cancel_post_capture_flow; pub mod capture_flow; pub mod complete_authorize_flow; +pub mod extend_authorization_flow; #[cfg(feature = "v2")] pub mod external_proxy_flow; pub mod incremental_authorization_flow; diff --git a/crates/router/src/core/payments/flows/extend_authorization_flow.rs b/crates/router/src/core/payments/flows/extend_authorization_flow.rs new file mode 100644 index 00000000000..fbf704820c8 --- /dev/null +++ b/crates/router/src/core/payments/flows/extend_authorization_flow.rs @@ -0,0 +1,151 @@ +use async_trait::async_trait; + +use super::{ConstructFlowSpecificData, Feature}; +use crate::{ + core::{ + errors::{ConnectorErrorExt, RouterResult}, + payments::{self, access_token, helpers, transformers, PaymentData}, + }, + routes::{metrics, SessionState}, + services, + types::{self, api, domain}, +}; + +#[async_trait] +impl + ConstructFlowSpecificData< + api::ExtendAuthorization, + types::PaymentsExtendAuthorizationData, + types::PaymentsResponseData, + > for PaymentData +{ + #[cfg(feature = "v2")] + async fn construct_router_data<'a>( + &self, + _state: &SessionState, + _connector_id: &str, + _merchant_context: &domain::MerchantContext, + _customer: &Option, + _merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails, + _merchant_recipient_data: Option, + _header_payload: Option, + ) -> RouterResult { + todo!() + } + + #[cfg(feature = "v1")] + async fn construct_router_data<'a>( + &self, + state: &SessionState, + connector_id: &str, + merchant_context: &domain::MerchantContext, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + merchant_recipient_data: Option, + header_payload: Option, + _payment_method: Option, + _payment_method_type: Option, + ) -> RouterResult { + Box::pin(transformers::construct_payment_router_data::< + api::ExtendAuthorization, + types::PaymentsExtendAuthorizationData, + >( + state, + self.clone(), + connector_id, + merchant_context, + customer, + merchant_connector_account, + merchant_recipient_data, + header_payload, + None, + None, + )) + .await + } +} + +#[async_trait] +impl Feature + for types::RouterData< + api::ExtendAuthorization, + types::PaymentsExtendAuthorizationData, + types::PaymentsResponseData, + > +{ + async fn decide_flows<'a>( + self, + state: &SessionState, + connector: &api::ConnectorData, + call_connector_action: payments::CallConnectorAction, + connector_request: Option, + _business_profile: &domain::Profile, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, + _return_raw_connector_response: Option, + ) -> RouterResult { + metrics::PAYMENT_EXTEND_AUTHORIZATION_COUNT.add( + 1, + router_env::metric_attributes!(("connector", connector.connector_name.to_string())), + ); + + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::ExtendAuthorization, + types::PaymentsExtendAuthorizationData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + let resp = services::execute_connector_processing_step( + state, + connector_integration, + &self, + call_connector_action, + connector_request, + None, + ) + .await + .to_payment_failed_response()?; + + Ok(resp) + } + + async fn add_access_token<'a>( + &self, + state: &SessionState, + connector: &api::ConnectorData, + merchant_context: &domain::MerchantContext, + creds_identifier: Option<&str>, + ) -> RouterResult { + Box::pin(access_token::add_access_token( + state, + connector, + merchant_context, + self, + creds_identifier, + )) + .await + } + + async fn build_flow_specific_connector_request( + &mut self, + state: &SessionState, + connector: &api::ConnectorData, + call_connector_action: payments::CallConnectorAction, + ) -> RouterResult<(Option, bool)> { + let request = match call_connector_action { + payments::CallConnectorAction::Trigger => { + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::ExtendAuthorization, + types::PaymentsExtendAuthorizationData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + connector_integration + .build_request(self, &state.conf.connectors) + .to_payment_failed_response()? + } + _ => None, + }; + + Ok((request, true)) + } +} diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 01023f2290d..7ed0aa58138 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -30,6 +30,8 @@ pub mod payment_update; #[cfg(feature = "v1")] pub mod payment_update_metadata; #[cfg(feature = "v1")] +pub mod payments_extend_authorization; +#[cfg(feature = "v1")] pub mod payments_incremental_authorization; #[cfg(feature = "v1")] pub mod tax_calculation; @@ -85,6 +87,7 @@ pub use self::{ payment_post_session_tokens::PaymentPostSessionTokens, payment_reject::PaymentReject, payment_session::PaymentSession, payment_start::PaymentStart, payment_status::PaymentStatus, payment_update::PaymentUpdate, payment_update_metadata::PaymentUpdateMetadata, + payments_extend_authorization::PaymentExtendAuthorization, payments_incremental_authorization::PaymentIncrementalAuthorization, tax_calculation::PaymentSessionUpdate, }; diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index f12d394f4ab..3f310bfd242 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -61,7 +61,7 @@ use crate::{ #[derive(Debug, Clone, Copy, router_derive::PaymentOperation)] #[operation( operations = "post_update_tracker", - flow = "sync_data, cancel_data, authorize_data, capture_data, complete_authorize_data, approve_data, reject_data, setup_mandate_data, session_data,incremental_authorization_data, sdk_session_update_data, post_session_tokens_data, update_metadata_data, cancel_post_capture_data" + flow = "sync_data, cancel_data, authorize_data, capture_data, complete_authorize_data, approve_data, reject_data, setup_mandate_data, session_data,incremental_authorization_data, sdk_session_update_data, post_session_tokens_data, update_metadata_data, cancel_post_capture_data, extend_authorization_data" )] pub struct PaymentResponse; @@ -1067,6 +1067,49 @@ impl PostUpdateTracker, types::PaymentsCancelPostCap } } +#[cfg(feature = "v1")] +#[async_trait] +impl PostUpdateTracker, types::PaymentsExtendAuthorizationData> + for PaymentResponse +{ + async fn update_tracker<'b>( + &'b self, + db: &'b SessionState, + mut payment_data: PaymentData, + router_data: types::RouterData< + F, + types::PaymentsExtendAuthorizationData, + types::PaymentsResponseData, + >, + key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + locale: &Option, + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec< + RoutableConnectorChoice, + >, + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile, + ) -> RouterResult> + where + F: 'b + Send, + { + payment_data = Box::pin(payment_response_update_tracker( + db, + payment_data, + router_data, + key_store, + storage_scheme, + locale, + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] + routable_connector, + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] + business_profile, + )) + .await?; + + Ok(payment_data) + } +} + #[cfg(feature = "v1")] #[async_trait] impl PostUpdateTracker, types::PaymentsApproveData> @@ -1142,7 +1185,6 @@ impl PostUpdateTracker, types::PaymentsRejectData> f Ok(payment_data) } } - #[cfg(feature = "v1")] #[async_trait] impl PostUpdateTracker, types::SetupMandateRequestData> @@ -1530,7 +1572,7 @@ async fn payment_response_update_tracker( None => // mark previous attempt status for technical failures in PSync flow { - if flow_name == "PSync" { + if flow_name == "PSync" || flow_name == "ExtendAuthorization" { match err.status_code { // marking failure for 2xx because this is genuine payment failure 200..=299 => enums::AttemptStatus::Failure, diff --git a/crates/router/src/core/payments/operations/payments_extend_authorization.rs b/crates/router/src/core/payments/operations/payments_extend_authorization.rs new file mode 100644 index 00000000000..0d62e209ed6 --- /dev/null +++ b/crates/router/src/core/payments/operations/payments_extend_authorization.rs @@ -0,0 +1,330 @@ +use std::marker::PhantomData; + +use api_models::enums::FrmSuggestion; +use async_trait::async_trait; +use error_stack::ResultExt; +use router_derive; +use router_env::{instrument, tracing}; + +use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; +use crate::{ + core::{ + errors::{self, RouterResult, StorageErrorExt}, + payments::{self, helpers, operations, PaymentData}, + }, + routes::{app::ReqState, SessionState}, + services, + types::{ + self as core_types, + api::{self, PaymentIdTypeExt}, + domain, + storage::{self, enums}, + }, + utils::OptionExt, +}; + +#[derive(Debug, Clone, Copy, router_derive::PaymentOperation)] +#[operation(operations = "all", flow = "extend_authorization")] +pub struct PaymentExtendAuthorization; + +type PaymentExtendAuthorizationOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsExtendAuthorizationRequest, PaymentData>; + +#[async_trait] +impl GetTracker, api::PaymentsExtendAuthorizationRequest> + for PaymentExtendAuthorization +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &api::PaymentIdType, + _request: &api::PaymentsExtendAuthorizationRequest, + merchant_context: &domain::MerchantContext, + _auth_flow: services::AuthFlow, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult< + operations::GetTrackerResponse< + 'a, + F, + api::PaymentsExtendAuthorizationRequest, + PaymentData, + >, + > { + let db = &*state.store; + let key_manager_state = &state.into(); + + let merchant_id = merchant_context.get_merchant_account().get_id(); + let storage_scheme = merchant_context.get_merchant_account().storage_scheme; + let payment_id = payment_id + .get_payment_intent_id() + .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + + let payment_intent = db + .find_payment_intent_by_payment_id_merchant_id( + key_manager_state, + &payment_id, + merchant_id, + merchant_context.get_merchant_key_store(), + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + helpers::validate_payment_status_against_allowed_statuses( + payment_intent.status, + &[ + enums::IntentStatus::RequiresCapture, + enums::IntentStatus::PartiallyCapturedAndCapturable, + enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture, + ], + "extend authorization", + )?; + + let payment_attempt = db + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &payment_intent.payment_id, + merchant_id, + payment_intent.active_attempt.get_id().as_str(), + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + if !payment_attempt + .request_extended_authorization + .is_some_and(|request_extended_authorization| request_extended_authorization.is_true()) + { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: + "You cannot extend the authorization for this payment because authorization extension is not enabled.".to_owned(), + })? + } + + let shipping_address = helpers::get_address_by_id( + state, + payment_intent.shipping_address_id.clone(), + merchant_context.get_merchant_key_store(), + &payment_intent.payment_id, + merchant_id, + merchant_context.get_merchant_account().storage_scheme, + ) + .await?; + + let billing_address = helpers::get_address_by_id( + state, + payment_intent.billing_address_id.clone(), + merchant_context.get_merchant_key_store(), + &payment_intent.payment_id, + merchant_id, + merchant_context.get_merchant_account().storage_scheme, + ) + .await?; + + let payment_method_billing = helpers::get_address_by_id( + state, + payment_attempt.payment_method_billing_address_id.clone(), + merchant_context.get_merchant_key_store(), + &payment_intent.payment_id, + merchant_id, + merchant_context.get_merchant_account().storage_scheme, + ) + .await?; + + let currency = payment_attempt.currency.get_required_value("currency")?; + let amount = payment_attempt.get_total_amount().into(); + + let profile_id = payment_intent + .profile_id + .as_ref() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("'profile_id' not set in payment intent")?; + + let business_profile = db + .find_business_profile_by_profile_id( + key_manager_state, + merchant_context.get_merchant_key_store(), + profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound { + id: profile_id.get_string_repr().to_owned(), + })?; + + let payment_data = PaymentData { + flow: PhantomData, + payment_intent, + payment_attempt, + currency, + amount, + email: None, + mandate_id: None, + mandate_connector: None, + setup_mandate: None, + customer_acceptance: None, + token: None, + token_data: None, + address: core_types::PaymentAddress::new( + shipping_address.as_ref().map(From::from), + billing_address.as_ref().map(From::from), + payment_method_billing.as_ref().map(From::from), + business_profile.use_billing_as_payment_method_billing, + ), + confirm: None, + payment_method_data: None, + payment_method_token: None, + payment_method_info: None, + force_sync: None, + all_keys_required: None, + refunds: vec![], + disputes: vec![], + attempts: None, + sessions_token: vec![], + card_cvc: None, + creds_identifier: None, + pm_token: None, + connector_customer_id: None, + recurring_mandate_payment_data: None, + ephemeral_key: None, + multiple_capture_data: None, + redirect_response: None, + surcharge_details: None, + frm_message: None, + payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], + authentication: None, + recurring_details: None, + poll_config: None, + tax_data: None, + session_id: None, + service_details: None, + card_testing_guard_data: None, + vault_operation: None, + threeds_method_comp_ind: None, + whole_connector_response: None, + is_manual_retry_enabled: None, + }; + + let get_trackers_response = operations::GetTrackerResponse { + operation: Box::new(self), + customer_details: None, + payment_data, + business_profile, + mandate_type: None, + }; + + Ok(get_trackers_response) + } +} + +#[async_trait] +impl Domain> + for PaymentExtendAuthorization +{ + #[instrument(skip_all)] + async fn get_or_create_customer_details<'a>( + &'a self, + _state: &SessionState, + _payment_data: &mut PaymentData, + _request: Option, + _merchant_key_store: &domain::MerchantKeyStore, + _storage_scheme: enums::MerchantStorageScheme, + ) -> errors::CustomResult< + ( + PaymentExtendAuthorizationOperation<'a, F>, + Option, + ), + errors::StorageError, + > { + Ok((Box::new(self), None)) + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + _state: &'a SessionState, + _payment_data: &mut PaymentData, + _storage_scheme: enums::MerchantStorageScheme, + _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, + _business_profile: &domain::Profile, + _should_retry_with_pan: bool, + ) -> RouterResult<( + PaymentExtendAuthorizationOperation<'a, F>, + Option, + Option, + )> { + Ok((Box::new(self), None, None)) + } + + async fn get_connector<'a>( + &'a self, + _merchant_context: &domain::MerchantContext, + state: &SessionState, + _request: &api::PaymentsExtendAuthorizationRequest, + _payment_intent: &storage::PaymentIntent, + ) -> errors::CustomResult { + helpers::get_connector_default(state, None).await + } + + #[instrument(skip_all)] + async fn guard_payment_against_blocklist<'a>( + &'a self, + _state: &SessionState, + _merchant_context: &domain::MerchantContext, + _payment_data: &mut PaymentData, + ) -> errors::CustomResult { + Ok(false) + } +} + +#[async_trait] +impl UpdateTracker, api::PaymentsExtendAuthorizationRequest> + for PaymentExtendAuthorization +{ + #[instrument(skip_all)] + async fn update_trackers<'b>( + &'b self, + _state: &'b SessionState, + _req_state: ReqState, + payment_data: PaymentData, + _customer: Option, + _storage_scheme: enums::MerchantStorageScheme, + _updated_customer: Option, + _key_store: &domain::MerchantKeyStore, + _frm_suggestion: Option, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult<(PaymentExtendAuthorizationOperation<'b, F>, PaymentData)> + where + F: 'b + Send, + { + Ok((Box::new(self), payment_data)) + } +} + +impl + ValidateRequest> + for PaymentExtendAuthorization +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + request: &api::PaymentsExtendAuthorizationRequest, + merchant_context: &'a domain::MerchantContext, + ) -> RouterResult<( + PaymentExtendAuthorizationOperation<'b, F>, + operations::ValidateResult, + )> { + Ok(( + Box::new(self), + operations::ValidateResult { + merchant_id: merchant_context.get_merchant_account().get_id().to_owned(), + payment_id: api::PaymentIdType::PaymentIntentId(request.payment_id.to_owned()), + storage_scheme: merchant_context.get_merchant_account().storage_scheme, + requeue: false, + }, + )) + } +} diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 53876336f08..80f81811995 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -4612,6 +4612,41 @@ impl TryFrom> for types::PaymentsAuthoriz } } +#[cfg(feature = "v2")] +impl TryFrom> for types::PaymentsExtendAuthorizationData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + todo!() + } +} + +#[cfg(feature = "v1")] +impl TryFrom> for types::PaymentsExtendAuthorizationData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + let payment_data = additional_data.payment_data; + let connector = api::ConnectorData::get_connector_by_name( + &additional_data.state.conf.connectors, + &additional_data.connector_name, + api::GetToken::Connector, + payment_data.payment_attempt.merchant_connector_id.clone(), + )?; + let amount = payment_data.payment_attempt.get_total_amount(); + + Ok(Self { + minor_amount: amount, + currency: payment_data.currency, + connector_transaction_id: connector + .connector + .connector_transaction_id(&payment_data.payment_attempt)? + .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, + connector_meta: payment_data.payment_attempt.connector_metadata, + }) + } +} + #[cfg(feature = "v2")] impl TryFrom> for types::PaymentsSyncData { type Error = error_stack::Report; diff --git a/crates/router/src/core/webhooks/recovery_incoming.rs b/crates/router/src/core/webhooks/recovery_incoming.rs index 4a8ce622255..939833ad941 100644 --- a/crates/router/src/core/webhooks/recovery_incoming.rs +++ b/crates/router/src/core/webhooks/recovery_incoming.rs @@ -1446,6 +1446,8 @@ impl RecoveryAction { | webhooks::IncomingWebhookEvent::MandateActive | webhooks::IncomingWebhookEvent::MandateRevoked | webhooks::IncomingWebhookEvent::EndpointVerification + | webhooks::IncomingWebhookEvent::ExtendedAuthorization + | webhooks::IncomingWebhookEvent::ExtendAuthorizationFailed | webhooks::IncomingWebhookEvent::ExternalAuthenticationARes | webhooks::IncomingWebhookEvent::FrmApproved | webhooks::IncomingWebhookEvent::FrmRejected diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 36a1b02b61c..0f864225f5f 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -947,6 +947,9 @@ impl Payments { .service( web::resource("/{payment_id}/incremental_authorization").route(web::post().to(payments::payments_incremental_authorization)), ) + .service( + web::resource("/{payment_id}/extend_authorization").route(web::post().to(payments::payments_extend_authorization)), + ) .service( web::resource("/{payment_id}/{merchant_id}/authorize/{connector}") .route(web::post().to(payments::post_3ds_payments_authorize)) diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index e7c9f97c000..4db69545800 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -152,6 +152,7 @@ impl From for ApiIdentifier { | Flow::PaymentsAggregate | Flow::PaymentsRedirect | Flow::PaymentsIncrementalAuthorization + | Flow::PaymentsExtendAuthorization | Flow::PaymentsExternalAuthentication | Flow::PaymentsAuthorize | Flow::GetExtendedCardInfo diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index defb73fa138..7eb8116b47b 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -26,6 +26,9 @@ counter_metric!(SUCCESSFUL_REFUND, GLOBAL_METER); counter_metric!(PAYMENT_CANCEL_COUNT, GLOBAL_METER); counter_metric!(SUCCESSFUL_CANCEL, GLOBAL_METER); +counter_metric!(PAYMENT_EXTEND_AUTHORIZATION_COUNT, GLOBAL_METER); +counter_metric!(SUCCESSFUL_EXTEND_AUTHORIZATION_COUNT, GLOBAL_METER); + counter_metric!(MANDATE_COUNT, GLOBAL_METER); counter_metric!(SUBSEQUENT_MANDATE_PAYMENT, GLOBAL_METER); diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 0cbfeff4c97..19a0268a7de 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -2302,6 +2302,58 @@ pub async fn payments_incremental_authorization( .await } +#[cfg(feature = "v1")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsExtendAuthorization, payment_id))] +pub async fn payments_extend_authorization( + state: web::Data, + req: actix_web::HttpRequest, + path: web::Path, +) -> impl Responder { + let flow = Flow::PaymentsExtendAuthorization; + let payment_id = path.into_inner(); + + tracing::Span::current().record("payment_id", payment_id.get_string_repr()); + let payload = payment_types::PaymentsExtendAuthorizationRequest { payment_id }; + + let locking_action = payload.get_locking_input(flow.clone()); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: auth::AuthenticationData, req, req_state| { + let merchant_context = domain::MerchantContext::NormalMerchant(Box::new( + domain::Context(auth.merchant_account, auth.key_store), + )); + payments::payments_core::< + api_types::ExtendAuthorization, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( + state, + req_state, + merchant_context, + auth.profile_id, + payments::PaymentExtendAuthorization, + req, + api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + HeaderPayload::default(), + ) + }, + &auth::HeaderAuth(auth::ApiKeyAuth { + is_connected_allowed: false, + is_platform_allowed: true, + }), + locking_action, + )) + .await +} + #[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsExternalAuthentication, payment_id))] pub async fn payments_external_authentication( @@ -2729,6 +2781,23 @@ impl GetLockingInput for payment_types::PaymentsCancelPostCaptureRequest { } } +#[cfg(feature = "v1")] +impl GetLockingInput for payment_types::PaymentsExtendAuthorizationRequest { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + lock_utils::ApiIdentifier: From, + { + api_locking::LockAction::Hold { + input: api_locking::LockingInput { + unique_locking_key: self.payment_id.get_string_repr().to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } +} + #[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsCaptureRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index d92633ecae7..50c7c6471ff 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -803,6 +803,7 @@ impl Authenticate for api_models::payments::PaymentsCancelRequest {} impl Authenticate for api_models::payments::PaymentsCancelPostCaptureRequest {} impl Authenticate for api_models::payments::PaymentsCaptureRequest {} impl Authenticate for api_models::payments::PaymentsIncrementalAuthorizationRequest {} +impl Authenticate for api_models::payments::PaymentsExtendAuthorizationRequest {} impl Authenticate for api_models::payments::PaymentsStartRequest {} // impl Authenticate for api_models::payments::PaymentsApproveRequest {} impl Authenticate for api_models::payments::PaymentsRejectRequest {} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 7320a2a4f03..6bde432a70b 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -37,10 +37,10 @@ use hyperswitch_domain_models::router_flow_types::{ mandate_revoke::MandateRevoke, payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, - CompleteAuthorize, CreateConnectorCustomer, CreateOrder, ExternalVaultProxy, - IncrementalAuthorization, InitPayment, PSync, PostCaptureVoid, PostProcessing, - PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, - UpdateMetadata, Void, + CompleteAuthorize, CreateConnectorCustomer, CreateOrder, ExtendAuthorization, + ExternalVaultProxy, IncrementalAuthorization, InitPayment, PSync, PostCaptureVoid, + PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, + SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -76,9 +76,9 @@ pub use hyperswitch_domain_models::{ FetchDisputesRequestData, MandateRevokeRequestData, MultipleCaptureRequestData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsExtendAuthorizationData, PaymentsIncrementalAuthorizationData, + PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, + PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, ResponseId, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, SplitRefundsRequest, SubmitEvidenceRequestData, SyncRequestType, UploadFileRequestData, VaultRequestData, @@ -155,6 +155,8 @@ pub type PaymentsIncrementalAuthorizationRouterData = RouterData< PaymentsIncrementalAuthorizationData, PaymentsResponseData, >; +pub type PaymentsExtendAuthorizationRouterData = + RouterData; pub type PaymentsTaxCalculationRouterData = RouterData; @@ -195,6 +197,12 @@ pub type PaymentsCancelResponseRouterData = ResponseRouterData; pub type PaymentsCancelPostCaptureResponseRouterData = ResponseRouterData; +pub type PaymentsExtendAuthorizationResponseRouterData = ResponseRouterData< + ExtendAuthorization, + R, + PaymentsExtendAuthorizationData, + PaymentsResponseData, +>; pub type PaymentsBalanceResponseRouterData = ResponseRouterData; pub type PaymentsSyncResponseRouterData = @@ -650,6 +658,50 @@ impl Capturable for PaymentsSyncData { } } } +impl Capturable for PaymentsExtendAuthorizationData { + fn get_captured_amount( + &self, + _amount_captured: Option, + payment_data: &PaymentData, + ) -> Option + where + F: Clone, + { + // return previously captured amount + payment_data + .payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()) + } + fn get_amount_capturable( + &self, + _payment_data: &PaymentData, + _amount_capturable: Option, + attempt_status: common_enums::AttemptStatus, + ) -> Option + where + F: Clone, + { + let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); + match intent_status { + common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture + | common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::Conflicted + | common_enums::IntentStatus::Expired => Some(0), + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation + | common_enums::IntentStatus::RequiresCapture + | common_enums::IntentStatus::PartiallyCapturedAndCapturable + | common_enums::IntentStatus::Processing + | common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture => None, + } + } +} pub struct AddAccessTokenResult { pub access_token_result: Result, ErrorResponse>, diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 4b3f448de19..1d336eb0623 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -23,24 +23,26 @@ pub use api_models::{ PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelPostCaptureRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, - PaymentsExternalAuthenticationRequest, PaymentsIncrementalAuthorizationRequest, - PaymentsManualUpdateRequest, PaymentsPostSessionTokensRequest, - PaymentsPostSessionTokensResponse, PaymentsRedirectRequest, PaymentsRedirectionResponse, - PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm, - PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, - PaymentsStartRequest, PaymentsUpdateMetadataRequest, PaymentsUpdateMetadataResponse, - PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, UrlDetails, - VaultSessionDetails, VerifyRequest, VerifyResponse, VgsSessionDetails, WalletData, + PaymentsExtendAuthorizationRequest, PaymentsExternalAuthenticationRequest, + PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, + PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, + PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRejectRequest, + PaymentsRequest, PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, + PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest, + PaymentsUpdateMetadataRequest, PaymentsUpdateMetadataResponse, PgRedirectResponse, + PhoneDetails, RedirectionResponse, SessionToken, UrlDetails, VaultSessionDetails, + VerifyRequest, VerifyResponse, VgsSessionDetails, WalletData, }, }; pub use common_types::payments::{AcceptanceType, CustomerAcceptance, OnlineMandate}; use error_stack::ResultExt; pub use hyperswitch_domain_models::router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, CreateOrder, ExternalVaultProxy, IncrementalAuthorization, - InitPayment, PSync, PaymentCreateIntent, PaymentGetIntent, PaymentMethodToken, - PaymentUpdateIntent, PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, - RecordAttempt, Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, + CreateConnectorCustomer, CreateOrder, ExtendAuthorization, ExternalVaultProxy, + IncrementalAuthorization, InitPayment, PSync, PaymentCreateIntent, PaymentGetIntent, + PaymentMethodToken, PaymentUpdateIntent, PostCaptureVoid, PostProcessing, PostSessionTokens, + PreProcessing, RecordAttempt, Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, + Void, }; pub use hyperswitch_interfaces::api::payments::{ ConnectorCustomer, MandateSetup, Payment, PaymentApprove, PaymentAuthorize, @@ -53,11 +55,11 @@ pub use hyperswitch_interfaces::api::payments::{ pub use super::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, - PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, - PaymentPostCaptureVoidV2, PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, - PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, - PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, - TaxCalculationV2, + PaymentAuthorizeV2, PaymentCaptureV2, PaymentExtendAuthorizationV2, + PaymentIncrementalAuthorizationV2, PaymentPostCaptureVoidV2, PaymentPostSessionTokensV2, + PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, + PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, + PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, }; use crate::core::errors; diff --git a/crates/router/src/types/api/payments_v2.rs b/crates/router/src/types/api/payments_v2.rs index ad0a615a609..db5fc39fcc8 100644 --- a/crates/router/src/types/api/payments_v2.rs +++ b/crates/router/src/types/api/payments_v2.rs @@ -1,8 +1,8 @@ pub use hyperswitch_interfaces::api::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, - PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, - PaymentPostCaptureVoidV2, PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, - PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, - PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, - TaxCalculationV2, + PaymentAuthorizeV2, PaymentCaptureV2, PaymentExtendAuthorizationV2, + PaymentIncrementalAuthorizationV2, PaymentPostCaptureVoidV2, PaymentPostSessionTokensV2, + PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, + PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, + PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, }; diff --git a/crates/router_derive/src/macros/operation.rs b/crates/router_derive/src/macros/operation.rs index 2e6dabe0b29..18ab93a553a 100644 --- a/crates/router_derive/src/macros/operation.rs +++ b/crates/router_derive/src/macros/operation.rs @@ -31,6 +31,8 @@ pub enum Derives { SessionData, IncrementalAuthorization, IncrementalAuthorizationData, + ExtendAuthorization, + ExtendAuthorizationData, SdkSessionUpdate, SdkSessionUpdateData, PostSessionTokens, @@ -137,6 +139,12 @@ impl Conversion { Derives::CancelPostCaptureData => { syn::Ident::new("PaymentsCancelPostCaptureData", Span::call_site()) } + Derives::ExtendAuthorization => { + syn::Ident::new("PaymentsExtendAuthorizationRequest", Span::call_site()) + } + Derives::ExtendAuthorizationData => { + syn::Ident::new("PaymentsExtendAuthorizationData", Span::call_site()) + } } } @@ -461,6 +469,7 @@ pub fn operation_derive_inner(input: DeriveInput) -> syn::Result syn::Result