diff --git a/api-reference/docs.json b/api-reference/docs.json index 17050ceb9fc..aa3cae620fc 100644 --- a/api-reference/docs.json +++ b/api-reference/docs.json @@ -42,6 +42,7 @@ "v1/payments/payments--confirm", "v1/payments/payments--retrieve", "v1/payments/payments--cancel", + "v1/payments/payments--cancel-post-capture", "v1/payments/payments--capture", "v1/payments/payments--incremental-authorization", "v1/payments/payments--session-token", diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index cf48cef794c..92e6eb893a0 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -1083,6 +1083,67 @@ ] } }, + "/payments/{payment_id}/cancel_post_capture": { + "post": { + "tags": [ + "Payments" + ], + "summary": "Payments - Cancel Post Capture", + "description": "A Payment could can be cancelled when it is in one of these statuses: `succeeded`, `partially_captured`, `partially_captured_and_capturable`.", + "operationId": "Cancel a Payment Post Capture", + "parameters": [ + { + "name": "payment_id", + "in": "path", + "description": "The identifier for payment", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsCancelPostCaptureRequest" + }, + "examples": { + "Cancel the payment post capture with cancellation reason": { + "value": { + "cancellation_reason": "requested_by_customer" + } + }, + "Cancel the payment post capture with minimal fields": { + "value": {} + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment canceled post capture" + }, + "400": { + "description": "Missing mandatory fields", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericErrorResponseOpenApi" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/payments/list": { "get": { "tags": [ @@ -7826,6 +7887,7 @@ "authorizing", "cod_initiated", "voided", + "voided_post_charge", "void_initiated", "capture_initiated", "capture_failed", @@ -14639,6 +14701,7 @@ "payment_failed", "payment_processing", "payment_cancelled", + "payment_cancelled_post_capture", "payment_authorized", "payment_captured", "payment_expired", @@ -16486,6 +16549,7 @@ "succeeded", "failed", "cancelled", + "cancelled_post_capture", "processing", "requires_customer_action", "requires_merchant_action", @@ -21937,6 +22001,17 @@ "recurring_mandate" ] }, + "PaymentsCancelPostCaptureRequest": { + "type": "object", + "description": "Request to cancel a payment when the payment is already captured", + "properties": { + "cancellation_reason": { + "type": "string", + "description": "The reason for the payment cancel", + "nullable": true + } + } + }, "PaymentsCancelRequest": { "type": "object", "properties": { @@ -31421,6 +31496,7 @@ "AUTHORIZING", "C_O_D_INITIATED", "VOIDED", + "VOIDED_POST_CHARGE", "VOID_INITIATED", "NOP", "CAPTURE_INITIATED", diff --git a/api-reference/v1/payments/payments--cancel-post-capture.mdx b/api-reference/v1/payments/payments--cancel-post-capture.mdx new file mode 100644 index 00000000000..74aa3bc14ee --- /dev/null +++ b/api-reference/v1/payments/payments--cancel-post-capture.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /payments/{payment_id}/cancel_post_capture +--- \ No newline at end of file diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index dc3578ef61c..7576cd7030a 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -4834,6 +4834,7 @@ "authorizing", "cod_initiated", "voided", + "voided_post_charge", "void_initiated", "capture_initiated", "capture_failed", @@ -10686,6 +10687,7 @@ "payment_failed", "payment_processing", "payment_cancelled", + "payment_cancelled_post_capture", "payment_authorized", "payment_captured", "payment_expired", @@ -12645,6 +12647,7 @@ "succeeded", "failed", "cancelled", + "cancelled_post_capture", "processing", "requires_customer_action", "requires_merchant_action", diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index ec4c8667f45..19f708c866f 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -12,8 +12,8 @@ use crate::{ payment_methods::PaymentMethodListResponse, payments::{ ExtendedCardInfoResponse, PaymentIdType, PaymentListFilterConstraints, - PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelRequest, - PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, + PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelPostCaptureRequest, + PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, @@ -132,6 +132,15 @@ impl ApiEventMetric for PaymentsCancelRequest { } } +#[cfg(feature = "v1")] +impl ApiEventMetric for PaymentsCancelPostCaptureRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} + #[cfg(feature = "v1")] impl ApiEventMetric for PaymentsApproveRequest { fn get_api_event_type(&self) -> Option { diff --git a/crates/api_models/src/open_router.rs b/crates/api_models/src/open_router.rs index 02ded9713f6..0365d4b6d50 100644 --- a/crates/api_models/src/open_router.rs +++ b/crates/api_models/src/open_router.rs @@ -221,6 +221,7 @@ pub enum TxnStatus { Authorizing, CODInitiated, Voided, + VoidedPostCharge, VoidInitiated, Nop, CaptureInitiated, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 0c238d18c2a..3419e780da6 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -7637,6 +7637,16 @@ pub struct PaymentsCancelRequest { pub merchant_connector_details: Option, } +/// Request to cancel a payment when the payment is already captured +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsCancelPostCaptureRequest { + /// The identifier for the payment + #[serde(skip)] + pub payment_id: id_type::PaymentId, + /// The reason for the payment cancel + pub cancellation_reason: Option, +} + #[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] pub struct PaymentsIncrementalAuthorizationRequest { /// The identifier for the payment diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 99b8c2e600e..e2ba3a88ca9 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -141,6 +141,7 @@ pub enum AttemptStatus { Authorizing, CodInitiated, Voided, + VoidedPostCharge, VoidInitiated, CaptureInitiated, CaptureFailed, @@ -166,6 +167,7 @@ impl AttemptStatus { | Self::Charged | Self::AutoRefunded | Self::Voided + | Self::VoidedPostCharge | Self::VoidFailed | Self::CaptureFailed | Self::Failure @@ -1501,6 +1503,7 @@ impl EventClass { EventType::PaymentFailed, EventType::PaymentProcessing, EventType::PaymentCancelled, + EventType::PaymentCancelledPostCapture, EventType::PaymentAuthorized, EventType::PaymentCaptured, EventType::PaymentExpired, @@ -1555,6 +1558,7 @@ pub enum EventType { PaymentFailed, PaymentProcessing, PaymentCancelled, + PaymentCancelledPostCapture, PaymentAuthorized, PaymentCaptured, PaymentExpired, @@ -1659,6 +1663,8 @@ pub enum IntentStatus { Failed, /// This payment has been cancelled. Cancelled, + /// This payment has been cancelled post capture. + CancelledPostCapture, /// This payment is still being processed by the payment processor. /// The status update might happen through webhooks or polling with the connector. Processing, @@ -1690,6 +1696,7 @@ impl IntentStatus { Self::Succeeded | Self::Failed | Self::Cancelled + | Self::CancelledPostCapture | Self::PartiallyCaptured | Self::Expired => true, Self::Processing @@ -1713,6 +1720,7 @@ impl IntentStatus { | Self::Succeeded | Self::Failed | Self::Cancelled + | Self::CancelledPostCapture | Self::PartiallyCaptured | Self::RequiresCapture | Self::Conflicted | Self::Expired=> false, Self::Processing @@ -1826,6 +1834,7 @@ impl From for PaymentMethodStatus { match attempt_status { AttemptStatus::Failure | AttemptStatus::Voided + | AttemptStatus::VoidedPostCharge | AttemptStatus::Started | AttemptStatus::Pending | AttemptStatus::Unresolved diff --git a/crates/common_enums/src/transformers.rs b/crates/common_enums/src/transformers.rs index 8d1549b80ae..15c0f958371 100644 --- a/crates/common_enums/src/transformers.rs +++ b/crates/common_enums/src/transformers.rs @@ -2123,6 +2123,7 @@ impl From for IntentStatus { | AttemptStatus::CaptureFailed | AttemptStatus::Failure => Self::Failed, AttemptStatus::Voided => Self::Cancelled, + AttemptStatus::VoidedPostCharge => Self::CancelledPostCapture, AttemptStatus::Expired => Self::Expired, } } @@ -2138,6 +2139,7 @@ impl From for Option { | IntentStatus::RequiresCustomerAction | IntentStatus::Conflicted => Some(EventType::ActionRequired), IntentStatus::Cancelled => Some(EventType::PaymentCancelled), + IntentStatus::CancelledPostCapture => Some(EventType::PaymentCancelledPostCapture), IntentStatus::Expired => Some(EventType::PaymentExpired), IntentStatus::PartiallyCaptured | IntentStatus::PartiallyCapturedAndCapturable => { Some(EventType::PaymentCaptured) diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs index 6bb4cf345f7..d0817482c83 100644 --- a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs @@ -697,6 +697,7 @@ impl TryFrom for ChargebeeRecordStatus { | enums::AttemptStatus::Authorizing | enums::AttemptStatus::CodInitiated | enums::AttemptStatus::Voided + | enums::AttemptStatus::VoidedPostCharge | enums::AttemptStatus::VoidInitiated | enums::AttemptStatus::CaptureInitiated | enums::AttemptStatus::VoidFailed diff --git a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs index 7c25b7a6e96..ca17c9f984c 100644 --- a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs @@ -2706,6 +2706,7 @@ impl TryFrom> | storage_enums::AttemptStatus::ConfirmationAwaited | storage_enums::AttemptStatus::DeviceDataCollectionPending | storage_enums::AttemptStatus::Voided + | storage_enums::AttemptStatus::VoidedPostCharge | storage_enums::AttemptStatus::Expired => 0, storage_enums::AttemptStatus::Charged | storage_enums::AttemptStatus::PartialCharged diff --git a/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs b/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs index 4aef739b979..7aa82e69da4 100644 --- a/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs @@ -269,6 +269,7 @@ impl TryFrom for RecurlyRecordStatus { | enums::AttemptStatus::Authorizing | enums::AttemptStatus::CodInitiated | enums::AttemptStatus::Voided + | enums::AttemptStatus::VoidedPostCharge | enums::AttemptStatus::VoidInitiated | enums::AttemptStatus::CaptureInitiated | enums::AttemptStatus::VoidFailed diff --git a/crates/hyperswitch_connectors/src/connectors/worldpayvantiv.rs b/crates/hyperswitch_connectors/src/connectors/worldpayvantiv.rs index 01736d1f474..fab73fb95c4 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpayvantiv.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpayvantiv.rs @@ -16,21 +16,24 @@ use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, Capture, PSync, PaymentMethodToken, PostCaptureVoid, Session, SetupMandate, + Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + PaymentsCancelData, PaymentsCancelPostCaptureData, PaymentsCaptureData, + PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{ ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, SupportedPaymentMethodsExt, }, types::{ - PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelPostCaptureRouterData, PaymentsCancelRouterData, + PaymentsCaptureRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -75,6 +78,7 @@ impl api::Refund for Worldpayvantiv {} impl api::RefundExecute for Worldpayvantiv {} impl api::RefundSync for Worldpayvantiv {} impl api::PaymentToken for Worldpayvantiv {} +impl api::PaymentPostCaptureVoid for Worldpayvantiv {} impl ConnectorIntegration for Worldpayvantiv @@ -544,6 +548,96 @@ impl ConnectorIntegration for Wo } } +impl ConnectorIntegration + for Worldpayvantiv +{ + fn get_headers( + &self, + req: &PaymentsCancelPostCaptureRouterData, + 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: &PaymentsCancelPostCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(self.base_url(connectors).to_owned()) + } + + fn get_request_body( + &self, + req: &PaymentsCancelPostCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req_object = worldpayvantiv::CnpOnlineRequest::try_from(req)?; + router_env::logger::info!(raw_connector_request=?connector_req_object); + + let connector_req = connector_utils::XmlSerializer::serialize_to_xml_bytes( + &connector_req_object, + worldpayvantiv::worldpayvantiv_constants::XML_VERSION, + Some(worldpayvantiv::worldpayvantiv_constants::XML_ENCODING), + None, + None, + )?; + + Ok(RequestContent::RawBytes(connector_req)) + } + + fn build_request( + &self, + req: &PaymentsCancelPostCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsPostCaptureVoidType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsPostCaptureVoidType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsPostCaptureVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelPostCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: worldpayvantiv::CnpOnlineResponse = + connector_utils::deserialize_xml_to_struct(&res.response)?; + 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, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl ConnectorIntegration for Worldpayvantiv { fn get_headers( &self, diff --git a/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs index 3fd7456e2ce..2e2251b1e8f 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs @@ -8,13 +8,13 @@ use hyperswitch_domain_models::{ }, router_flow_types::refunds::{Execute, RSync}, router_request_types::{ - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, - ResponseId, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, + PaymentsCaptureData, PaymentsSyncData, ResponseId, }, router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelPostCaptureRouterData, PaymentsCancelRouterData, + PaymentsCaptureRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{consts, errors}; @@ -79,6 +79,8 @@ pub enum OperationId { Auth, Capture, Void, + // VoidPostCapture + VoidPC, Refund, } @@ -128,6 +130,8 @@ pub struct CnpOnlineRequest { #[serde(skip_serializing_if = "Option::is_none")] pub auth_reversal: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub void: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub credit: Option, } @@ -137,6 +141,16 @@ pub struct Authentication { pub password: Secret, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Void { + #[serde(rename = "@id")] + pub id: String, + #[serde(rename = "@reportGroup")] + pub report_group: String, + pub cnp_txn_id: String, +} + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AuthReversal { @@ -586,6 +600,7 @@ impl TryFrom<&WorldpayvantivRouterData<&PaymentsAuthorizeRouterData>> for CnpOnl capture: None, auth_reversal: None, credit: None, + void: None, }) } } @@ -692,6 +707,7 @@ impl TryFrom<&WorldpayvantivRouterData<&PaymentsCaptureRouterData>> for CnpOnlin capture, auth_reversal: None, credit: None, + void: None, }) } } @@ -747,6 +763,7 @@ impl TryFrom<&WorldpayvantivRouterData<&RefundsRouterData>> for CnpOnlineR capture: None, auth_reversal: None, credit, + void: None, }) } } @@ -770,6 +787,7 @@ pub struct CnpOnlineResponse { pub sale_response: Option, pub capture_response: Option, pub auth_reversal_response: Option, + pub void_response: Option, pub credit_response: Option, } @@ -896,6 +914,21 @@ pub struct AuthReversalResponse { pub location: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VoidResponse { + #[serde(rename = "@id")] + pub id: String, + #[serde(rename = "@reportGroup")] + pub report_group: String, + pub cnp_txn_id: String, + pub response: WorldpayvantivResponseCode, + pub response_time: String, + pub post_date: Option, + pub message: String, + pub location: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreditResponse { @@ -1049,6 +1082,84 @@ impl TryFrom + TryFrom< + ResponseRouterData< + F, + CnpOnlineResponse, + PaymentsCancelPostCaptureData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + CnpOnlineResponse, + PaymentsCancelPostCaptureData, + PaymentsResponseData, + >, + ) -> Result { + match item.response.void_response { + Some(void_response) => { + let status = + get_attempt_status(WorldpayvantivPaymentFlow::VoidPC, void_response.response)?; + if connector_utils::is_payment_failure(status) { + Ok(Self { + status, + response: Err(ErrorResponse { + code: void_response.response.to_string(), + message: void_response.message.clone(), + reason: Some(void_response.message.clone()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(void_response.cnp_txn_id), + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }), + ..item.data + }) + } else { + Ok(Self { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + void_response.cnp_txn_id, + ), + 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, + }), + ..item.data + }) + } + } + None => Ok(Self { + // Incase of API failure + status: common_enums::AttemptStatus::VoidFailed, + response: Err(ErrorResponse { + code: item.response.response_code, + message: item.response.message.clone(), + reason: Some(item.response.message.clone()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }), + ..item.data + }), + } + } +} + impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( @@ -1136,6 +1247,48 @@ impl TryFrom<&PaymentsCancelRouterData> for CnpOnlineRequest { capture: None, auth_reversal, credit: None, + void: None, + }) + } +} + +impl TryFrom<&PaymentsCancelPostCaptureRouterData> for CnpOnlineRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsCancelPostCaptureRouterData) -> Result { + let report_group_metadata: WorldpayvantivPaymentMetadata = + connector_utils::to_connector_meta(item.request.connector_meta.clone())?; + let report_group = report_group_metadata.report_group.clone().ok_or( + errors::ConnectorError::RequestEncodingFailedWithReason( + "Failed to obtain report_group from metadata".to_string(), + ), + )?; + let void = Some(Void { + id: format!( + "{}_{}", + OperationId::VoidPC, + item.connector_request_reference_id + ), + report_group, + cnp_txn_id: item.request.connector_transaction_id.clone(), + }); + + let worldpayvantiv_auth_type = WorldpayvantivAuthType::try_from(&item.connector_auth_type)?; + let authentication = Authentication { + user: worldpayvantiv_auth_type.user, + password: worldpayvantiv_auth_type.password, + }; + + Ok(Self { + version: worldpayvantiv_constants::WORLDPAYVANTIV_VERSION.to_string(), + xmlns: worldpayvantiv_constants::XMLNS.to_string(), + merchant_id: worldpayvantiv_auth_type.merchant_id, + authentication, + authorization: None, + sale: None, + capture: None, + void, + auth_reversal: None, + credit: None, }) } } @@ -1340,6 +1493,9 @@ fn determine_attempt_status( } WorldpayvantivPaymentFlow::Auth => Ok(common_enums::AttemptStatus::Authorized), WorldpayvantivPaymentFlow::Void => Ok(common_enums::AttemptStatus::Voided), + WorldpayvantivPaymentFlow::VoidPC => { + Ok(common_enums::AttemptStatus::VoidedPostCharge) + } }, PaymentStatus::TransactionDeclined => match flow_type { WorldpayvantivPaymentFlow::Sale | WorldpayvantivPaymentFlow::Capture => { @@ -1349,6 +1505,7 @@ fn determine_attempt_status( Ok(common_enums::AttemptStatus::AuthorizationFailed) } WorldpayvantivPaymentFlow::Void => Ok(common_enums::AttemptStatus::VoidFailed), + WorldpayvantivPaymentFlow::VoidPC => Ok(common_enums::AttemptStatus::VoidFailed), }, PaymentStatus::PaymentStatusNotFound | PaymentStatus::NotYetProcessed @@ -2456,6 +2613,8 @@ pub enum WorldpayvantivPaymentFlow { Auth, Capture, Void, + //VoidPostCapture + VoidPC, } fn get_payment_flow_type(input: &str) -> Result { @@ -2463,6 +2622,8 @@ fn get_payment_flow_type(input: &str) -> Result Ok(common_enums::AttemptStatus::Authorizing), WorldpayvantivPaymentFlow::Capture => Ok(common_enums::AttemptStatus::CaptureInitiated), WorldpayvantivPaymentFlow::Void => Ok(common_enums::AttemptStatus::VoidInitiated), + WorldpayvantivPaymentFlow::VoidPC => { + Ok(common_enums::AttemptStatus::VoidInitiated) + } }, WorldpayvantivResponseCode::ShopperCheckoutExpired | WorldpayvantivResponseCode::ProcessingNetworkUnavailable @@ -2847,7 +3011,8 @@ fn get_attempt_status( WorldpayvantivPaymentFlow::Sale => Ok(common_enums::AttemptStatus::Failure), WorldpayvantivPaymentFlow::Auth => Ok(common_enums::AttemptStatus::AuthorizationFailed), WorldpayvantivPaymentFlow::Capture => Ok(common_enums::AttemptStatus::CaptureFailed), - WorldpayvantivPaymentFlow::Void => Ok(common_enums::AttemptStatus::VoidFailed) + WorldpayvantivPaymentFlow::Void => Ok(common_enums::AttemptStatus::VoidFailed), + WorldpayvantivPaymentFlow::VoidPC => Ok(common_enums::AttemptStatus::VoidFailed) } } } diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 67821e66d72..74661c0ecae 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -52,8 +52,9 @@ use hyperswitch_domain_models::{ mandate_revoke::MandateRevoke, payments::{ Approve, AuthorizeSessionToken, CalculateTax, CompleteAuthorize, - CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, PostProcessing, - PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, UpdateMetadata, + CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, PostCaptureVoid, + PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, + UpdateMetadata, }, webhooks::VerifyWebhookSource, Authenticate, AuthenticationConfirmation, ExternalVaultCreateFlow, ExternalVaultDeleteFlow, @@ -68,11 +69,12 @@ use hyperswitch_domain_models::{ }, AcceptDisputeRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData, DefendDisputeRequestData, - MandateRevokeRequestData, PaymentsApproveData, PaymentsIncrementalAuthorizationData, - PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, - PaymentsRejectData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, - RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SubmitEvidenceRequestData, - UploadFileRequestData, VaultRequestData, VerifyWebhookSourceRequestData, + MandateRevokeRequestData, PaymentsApproveData, PaymentsCancelPostCaptureData, + PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RetrieveFileRequestData, + SdkPaymentsSessionUpdateData, SubmitEvidenceRequestData, UploadFileRequestData, + VaultRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, AuthenticationResponseData, DefendDisputeResponse, @@ -110,8 +112,8 @@ use hyperswitch_interfaces::{ files::{FileUpload, RetrieveFile, UploadFile}, payments::{ ConnectorCustomer, PaymentApprove, PaymentAuthorizeSessionToken, - PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, - PaymentSessionUpdate, PaymentUpdateMetadata, PaymentsCompleteAuthorize, + PaymentIncrementalAuthorization, PaymentPostCaptureVoid, PaymentPostSessionTokens, + PaymentReject, PaymentSessionUpdate, PaymentUpdateMetadata, PaymentsCompleteAuthorize, PaymentsCreateOrder, PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, }, revenue_recovery::RevenueRecovery, @@ -959,6 +961,145 @@ default_imp_for_update_metadata!( connectors::CtpMastercard ); +macro_rules! default_imp_for_cancel_post_capture { + ($($path:ident::$connector:ident),*) => { + $( impl PaymentPostCaptureVoid for $path::$connector {} + impl + ConnectorIntegration< + PostCaptureVoid, + PaymentsCancelPostCaptureData, + PaymentsResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_cancel_post_capture!( + connectors::Aci, + connectors::Adyen, + 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::Bluecode, + 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::Fiserv, + connectors::Fiservemea, + connectors::Forte, + connectors::Getnet, + connectors::Helcim, + connectors::HyperswitchVault, + connectors::Iatapay, + connectors::Inespay, + connectors::Itaubank, + connectors::Jpmorgan, + connectors::Juspaythreedsserver, + connectors::Katapult, + connectors::Klarna, + connectors::Paypal, + connectors::Rapyd, + connectors::Razorpay, + connectors::Recurly, + connectors::Redsys, + connectors::Santander, + connectors::Shift4, + connectors::Silverflow, + connectors::Signifyd, + connectors::Square, + connectors::Stax, + connectors::Stripe, + connectors::Stripebilling, + connectors::Taxjar, + 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::Opayo, + connectors::Opennode, + connectors::Nuvei, + connectors::Nmi, + connectors::Paybox, + connectors::Payeezy, + connectors::Payload, + connectors::Payme, + connectors::Paystack, + connectors::Paytm, + connectors::Payu, + 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::Worldpayxml, + connectors::Wellsfargo, + connectors::Wellsfargopayout, + connectors::Xendit, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Riskified, + connectors::Threedsecureio, + connectors::Thunes, + connectors::Tokenio, + connectors::Trustpay, + connectors::Trustpayments, + connectors::Tsys, + connectors::UnifiedAuthenticationService, + connectors::Deutschebank, + connectors::Vgs, + connectors::Volt, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard +); + use crate::connectors; macro_rules! default_imp_for_complete_authorize { ($($path:ident::$connector:ident),*) => { @@ -7395,6 +7536,15 @@ impl { } +#[cfg(feature = "dummy_connector")] +impl PaymentPostCaptureVoid 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 72cb5a3cf3b..fedb562e516 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -18,8 +18,8 @@ use hyperswitch_domain_models::{ payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, PSync, - PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, Reject, - SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, + PaymentMethodToken, PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, + Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, revenue_recovery::{ @@ -38,10 +38,10 @@ use hyperswitch_domain_models::{ AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData, DefendDisputeRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, - PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, + PaymentsCancelPostCaptureData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, + PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, + PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, UploadFileRequestData, VaultRequestData, VerifyWebhookSourceRequestData, @@ -98,10 +98,11 @@ use hyperswitch_interfaces::{ payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentCreateOrderV2, - PaymentIncrementalAuthorizationV2, PaymentPostSessionTokensV2, PaymentRejectV2, - PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, - PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, - PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, + PaymentIncrementalAuthorizationV2, PaymentPostCaptureVoidV2, + PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, + PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, + PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + TaxCalculationV2, }, refunds_v2::{RefundExecuteV2, RefundSyncV2, RefundV2}, revenue_recovery_v2::{ @@ -127,6 +128,7 @@ macro_rules! default_imp_for_new_connector_integration_payment { impl PaymentAuthorizeSessionTokenV2 for $path::$connector{} impl PaymentSyncV2 for $path::$connector{} impl PaymentVoidV2 for $path::$connector{} + impl PaymentPostCaptureVoidV2 for $path::$connector{} impl PaymentApproveV2 for $path::$connector{} impl PaymentRejectV2 for $path::$connector{} impl PaymentCaptureV2 for $path::$connector{} @@ -154,6 +156,9 @@ macro_rules! default_imp_for_new_connector_integration_payment { ConnectorIntegrationV2 for $path::$connector{} impl + ConnectorIntegrationV2 + for $path::$connector{} + impl ConnectorIntegrationV2 for $path::$connector{} impl diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index d2ee231d144..5f262a90acd 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -436,6 +436,7 @@ pub(crate) fn is_payment_failure(status: AttemptStatus) -> bool { | AttemptStatus::Authorizing | AttemptStatus::CodInitiated | AttemptStatus::Voided + | AttemptStatus::VoidedPostCharge | AttemptStatus::VoidInitiated | AttemptStatus::CaptureInitiated | AttemptStatus::AutoRefunded @@ -6327,6 +6328,7 @@ impl FrmTransactionRouterDataRequest for FrmTransactionRouterData { | AttemptStatus::RouterDeclined | AttemptStatus::AuthorizationFailed | AttemptStatus::Voided + | AttemptStatus::VoidedPostCharge | AttemptStatus::CaptureFailed | AttemptStatus::Failure | AttemptStatus::AutoRefunded diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 8cdcfe7e456..572ddcc30c2 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -702,6 +702,7 @@ impl common_enums::IntentStatus::Succeeded | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()), // For these statuses, update the capturable amount when it reaches terminal / capturable state @@ -739,6 +740,7 @@ impl } // No amount is captured common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()), // For these statuses, update the amount captured when it reaches terminal state @@ -914,6 +916,7 @@ impl common_enums::IntentStatus::Succeeded | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()), // For these statuses, update the capturable amount when it reaches terminal / capturable state @@ -954,6 +957,7 @@ impl } // No amount is captured common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()), common_enums::IntentStatus::RequiresCapture => { @@ -1151,6 +1155,7 @@ impl common_enums::IntentStatus::Succeeded | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()), // For these statuses, update the capturable amount when it reaches terminal / capturable state @@ -1190,6 +1195,7 @@ impl } // No amount is captured common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()), // For these statuses, update the amount captured when it reaches terminal state @@ -1385,6 +1391,7 @@ impl common_enums::IntentStatus::Succeeded | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()), // For these statuses, update the capturable amount when it reaches terminal / capturable state @@ -1422,6 +1429,7 @@ impl } // No amount is captured common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()), // For these statuses, update the amount captured when it reaches terminal state 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 660c9fae4e2..1129f529971 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs @@ -26,6 +26,9 @@ pub struct PSync; #[derive(Debug, Clone)] pub struct Void; +#[derive(Debug, Clone)] +pub struct PostCaptureVoid; + #[derive(Debug, Clone)] pub struct Reject; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index c33603ab279..2d0199c2664 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -567,6 +567,16 @@ pub struct PaymentsCancelData { pub capture_method: Option, } +#[derive(Debug, Default, Clone)] +pub struct PaymentsCancelPostCaptureData { + pub currency: Option, + pub connector_transaction_id: String, + pub cancellation_reason: Option, + pub connector_meta: Option, + // minor amount data for amount framework + pub minor_amount: 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 1e7b6f23184..2dc507cf200 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -8,9 +8,9 @@ use crate::{ Authenticate, AuthenticationConfirmation, Authorize, AuthorizeSessionToken, BillingConnectorInvoiceSync, BillingConnectorPaymentsSync, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, CreateOrder, Execute, IncrementalAuthorization, - PSync, PaymentMethodToken, PostAuthenticate, PostSessionTokens, PreAuthenticate, - PreProcessing, RSync, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, - VerifyWebhookSource, Void, + PSync, PaymentMethodToken, PostAuthenticate, PostCaptureVoid, PostSessionTokens, + PreAuthenticate, PreProcessing, RSync, SdkSessionUpdate, Session, SetupMandate, + UpdateMetadata, VerifyWebhookSource, Void, }, router_request_types::{ revenue_recovery::{ @@ -25,9 +25,9 @@ use crate::{ AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, - PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData, - PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, - PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, + PaymentsCancelPostCaptureData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, VaultRequestData, VerifyWebhookSourceRequestData, }, @@ -52,6 +52,8 @@ pub type PaymentsPreProcessingRouterData = pub type PaymentsSyncRouterData = RouterData; pub type PaymentsCaptureRouterData = RouterData; pub type PaymentsCancelRouterData = RouterData; +pub type PaymentsCancelPostCaptureRouterData = + RouterData; pub type SetupMandateRouterData = RouterData; pub type RefundsRouterData = RouterData; diff --git a/crates/hyperswitch_interfaces/src/api/payments.rs b/crates/hyperswitch_interfaces/src/api/payments.rs index 8b017be37da..b923d85a9b6 100644 --- a/crates/hyperswitch_interfaces/src/api/payments.rs +++ b/crates/hyperswitch_interfaces/src/api/payments.rs @@ -5,16 +5,16 @@ use hyperswitch_domain_models::{ payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, UpdateMetadata, Void, + PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, Reject, + SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, CreateOrder, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData, PaymentMethodTokenizationData, PaymentsApproveData, - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, + PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, @@ -35,6 +35,7 @@ pub trait Payment: + PaymentSync + PaymentCapture + PaymentVoid + + PaymentPostCaptureVoid + PaymentApprove + PaymentReject + MandateSetup @@ -87,6 +88,12 @@ pub trait PaymentVoid: { } +/// trait PaymentPostCaptureVoid +pub trait PaymentPostCaptureVoid: + api::ConnectorIntegration +{ +} + /// 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 dbc7b364791..63a876c2128 100644 --- a/crates/hyperswitch_interfaces/src/api/payments_v2.rs +++ b/crates/hyperswitch_interfaces/src/api/payments_v2.rs @@ -5,14 +5,14 @@ use hyperswitch_domain_models::{ router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, UpdateMetadata, Void, + PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, Reject, + SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData, PaymentMethodTokenizationData, PaymentsApproveData, - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, + PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, @@ -53,6 +53,17 @@ pub trait PaymentVoidV2: { } +/// trait PaymentPostCaptureVoidV2 +pub trait PaymentPostCaptureVoidV2: + ConnectorIntegrationV2< + PostCaptureVoid, + PaymentFlowData, + PaymentsCancelPostCaptureData, + PaymentsResponseData, +> +{ +} + /// trait PaymentApproveV2 pub trait PaymentApproveV2: ConnectorIntegrationV2 @@ -210,6 +221,7 @@ pub trait PaymentV2: + PaymentSyncV2 + PaymentCaptureV2 + PaymentVoidV2 + + PaymentPostCaptureVoidV2 + PaymentApproveV2 + PaymentRejectV2 + MandateSetupV2 diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index d2f90998c80..c23a4c6c6ce 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -11,8 +11,8 @@ use hyperswitch_domain_models::{ payments::{ Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, InitPayment, PSync, - PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, SdkSessionUpdate, - Session, SetupMandate, UpdateMetadata, Void, + PaymentMethodToken, PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, + SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, revenue_recovery::{BillingConnectorPaymentsSync, RecoveryRecordBack}, @@ -39,8 +39,8 @@ use hyperswitch_domain_models::{ AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData, DefendDisputeRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCancelPostCaptureData, + PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, @@ -139,6 +139,9 @@ pub type PaymentsSessionType = /// Type alias for `ConnectorIntegration` pub type PaymentsVoidType = dyn ConnectorIntegration; +/// Type alias for `ConnectorIntegration` +pub type PaymentsPostCaptureVoidType = + dyn ConnectorIntegration; /// Type alias for `ConnectorIntegration` pub type TokenizationType = dyn ConnectorIntegration< diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 58e178f5b9a..b9c53dfa28e 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -78,6 +78,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_capture, routes::payments::payments_connector_session, routes::payments::payments_cancel, + routes::payments::payments_cancel_post_capture, routes::payments::payments_list, routes::payments::payments_incremental_authorization, routes::payment_link::payment_link_retrieve, @@ -532,6 +533,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SamsungPayTokenData, api_models::payments::ApplepayPaymentMethod, api_models::payments::PaymentsCancelRequest, + api_models::payments::PaymentsCancelPostCaptureRequest, 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 70c83f23392..d3abd208e9a 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -812,6 +812,40 @@ pub fn payments_connector_session() {} )] pub fn payments_cancel() {} +/// Payments - Cancel Post Capture +/// +/// A Payment could can be cancelled when it is in one of these statuses: `succeeded`, `partially_captured`, `partially_captured_and_capturable`. +#[utoipa::path( + post, + path = "/payments/{payment_id}/cancel_post_capture", + request_body ( + content = PaymentsCancelPostCaptureRequest, + examples( + ( + "Cancel the payment post capture with minimal fields" = ( + value = json!({}) + ) + ), + ( + "Cancel the payment post capture with cancellation reason" = ( + value = json!({"cancellation_reason": "requested_by_customer"}) + ) + ), + ) + ), + params( + ("payment_id" = String, Path, description = "The identifier for payment") + ), + responses( + (status = 200, description = "Payment canceled post capture"), + (status = 400, description = "Missing mandatory fields", body = GenericErrorResponseOpenApi) + ), + tag = "Payments", + operation_id = "Cancel a Payment Post Capture", + security(("api_key" = [])) +)] +pub fn payments_cancel_post_capture() {} + /// Payments - List /// /// To list the *payments* diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index acf2f5e03ab..b67988a1503 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -430,7 +430,9 @@ impl From for StripePaymentStatus { api_enums::IntentStatus::RequiresConfirmation => Self::RequiresConfirmation, api_enums::IntentStatus::RequiresCapture | api_enums::IntentStatus::PartiallyCapturedAndCapturable => Self::RequiresCapture, - api_enums::IntentStatus::Cancelled => Self::Canceled, + api_enums::IntentStatus::Cancelled | api_enums::IntentStatus::CancelledPostCapture => { + Self::Canceled + } } } } diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index 90df416ec76..531977842ef 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -324,7 +324,9 @@ impl From for StripeSetupStatus { logger::error!("Invalid status change"); Self::Canceled } - api_enums::IntentStatus::Cancelled => Self::Canceled, + api_enums::IntentStatus::Cancelled | api_enums::IntentStatus::CancelledPostCapture => { + Self::Canceled + } } } } diff --git a/crates/router/src/compatibility/stripe/webhooks.rs b/crates/router/src/compatibility/stripe/webhooks.rs index 5dad60120ec..196ab4cc43b 100644 --- a/crates/router/src/compatibility/stripe/webhooks.rs +++ b/crates/router/src/compatibility/stripe/webhooks.rs @@ -271,6 +271,7 @@ fn get_stripe_event_type(event_type: api_models::enums::EventType) -> &'static s api_models::enums::EventType::PaymentFailed => "payment_intent.payment_failed", api_models::enums::EventType::PaymentProcessing => "payment_intent.processing", api_models::enums::EventType::PaymentCancelled + | api_models::enums::EventType::PaymentCancelledPostCapture | api_models::enums::EventType::PaymentExpired => "payment_intent.canceled", // the below are not really stripe compatible because stripe doesn't provide this diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 5deeeadb41f..016f950fbca 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -2193,6 +2193,7 @@ impl FrmTransactionRouterDataRequest for fraud_check::FrmTransactionRouterData { | storage_enums::AttemptStatus::RouterDeclined | storage_enums::AttemptStatus::AuthorizationFailed | storage_enums::AttemptStatus::Voided + | storage_enums::AttemptStatus::VoidedPostCharge | storage_enums::AttemptStatus::CaptureFailed | storage_enums::AttemptStatus::Failure | storage_enums::AttemptStatus::AutoRefunded @@ -2238,6 +2239,7 @@ pub fn is_payment_failure(status: enums::AttemptStatus) -> bool { | common_enums::AttemptStatus::Authorizing | common_enums::AttemptStatus::CodInitiated | common_enums::AttemptStatus::Voided + | common_enums::AttemptStatus::VoidedPostCharge | common_enums::AttemptStatus::VoidInitiated | common_enums::AttemptStatus::CaptureInitiated | common_enums::AttemptStatus::AutoRefunded diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index a912dc27b82..9e9700e7a5a 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -81,9 +81,9 @@ use time; #[cfg(feature = "v1")] pub use self::operations::{ - PaymentApprove, PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, - PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, PaymentSession, - PaymentSessionUpdate, PaymentStatus, PaymentUpdate, PaymentUpdateMetadata, + PaymentApprove, PaymentCancel, PaymentCancelPostCapture, PaymentCapture, PaymentConfirm, + PaymentCreate, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, + PaymentSession, PaymentSessionUpdate, PaymentStatus, PaymentUpdate, PaymentUpdateMetadata, }; use self::{ conditional_configs::perform_decision_management, @@ -3137,6 +3137,7 @@ impl ValidateStatusForOperation for &PaymentRedirectSync { | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Processing | common_enums::IntentStatus::RequiresPaymentMethod | common_enums::IntentStatus::RequiresMerchantAction @@ -6881,6 +6882,12 @@ where storage_enums::IntentStatus::RequiresCapture | storage_enums::IntentStatus::PartiallyCapturedAndCapturable ), + "PaymentCancelPostCapture" => matches!( + payment_data.get_payment_intent().status, + storage_enums::IntentStatus::Succeeded + | storage_enums::IntentStatus::PartiallyCaptured + | storage_enums::IntentStatus::PartiallyCapturedAndCapturable + ), "PaymentCapture" => { matches!( payment_data.get_payment_intent().status, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 2cae2f0600d..039dda34771 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -1,6 +1,7 @@ pub mod approve_flow; pub mod authorize_flow; pub mod cancel_flow; +pub mod cancel_post_capture_flow; pub mod capture_flow; pub mod complete_authorize_flow; pub mod incremental_authorization_flow; diff --git a/crates/router/src/core/payments/flows/cancel_post_capture_flow.rs b/crates/router/src/core/payments/flows/cancel_post_capture_flow.rs new file mode 100644 index 00000000000..67ccd612a9d --- /dev/null +++ b/crates/router/src/core/payments/flows/cancel_post_capture_flow.rs @@ -0,0 +1,141 @@ +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::PostCaptureVoid, + types::PaymentsCancelPostCaptureData, + 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, + ) -> RouterResult { + Box::pin(transformers::construct_payment_router_data::< + api::PostCaptureVoid, + types::PaymentsCancelPostCaptureData, + >( + state, + self.clone(), + connector_id, + merchant_context, + customer, + merchant_connector_account, + merchant_recipient_data, + header_payload, + )) + .await + } +} + +#[async_trait] +impl Feature + for types::RouterData< + api::PostCaptureVoid, + types::PaymentsCancelPostCaptureData, + 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_CANCEL_COUNT.add( + 1, + router_env::metric_attributes!(("connector", connector.connector_name.to_string())), + ); + + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::PostCaptureVoid, + types::PaymentsCancelPostCaptureData, + 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 { + 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::PostCaptureVoid, + types::PaymentsCancelPostCaptureData, + 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/helpers.rs b/crates/router/src/core/payments/helpers.rs index 0e7ccdcac8e..8e44424339e 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4398,6 +4398,7 @@ pub fn get_attempt_type( | enums::AttemptStatus::PartialCharged | enums::AttemptStatus::PartialChargedAndChargeable | enums::AttemptStatus::Voided + | enums::AttemptStatus::VoidedPostCharge | enums::AttemptStatus::AutoRefunded | enums::AttemptStatus::PaymentMethodAwaited | enums::AttemptStatus::DeviceDataCollectionPending @@ -4453,6 +4454,7 @@ pub fn get_attempt_type( } } enums::IntentStatus::Cancelled + | enums::IntentStatus::CancelledPostCapture | enums::IntentStatus::RequiresCapture | enums::IntentStatus::PartiallyCaptured | enums::IntentStatus::PartiallyCapturedAndCapturable @@ -4703,6 +4705,7 @@ pub fn is_manual_retry_allowed( | enums::AttemptStatus::PartialCharged | enums::AttemptStatus::PartialChargedAndChargeable | enums::AttemptStatus::Voided + | enums::AttemptStatus::VoidedPostCharge | enums::AttemptStatus::AutoRefunded | enums::AttemptStatus::PaymentMethodAwaited | enums::AttemptStatus::DeviceDataCollectionPending @@ -4721,6 +4724,7 @@ pub fn is_manual_retry_allowed( | enums::AttemptStatus::Failure => Some(true), }, enums::IntentStatus::Cancelled + | enums::IntentStatus::CancelledPostCapture | enums::IntentStatus::RequiresCapture | enums::IntentStatus::PartiallyCaptured | enums::IntentStatus::PartiallyCapturedAndCapturable diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 9823c7abee3..6a21d1b6d85 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -3,6 +3,8 @@ pub mod payment_approve; #[cfg(feature = "v1")] pub mod payment_cancel; #[cfg(feature = "v1")] +pub mod payment_cancel_post_capture; +#[cfg(feature = "v1")] pub mod payment_capture; #[cfg(feature = "v1")] pub mod payment_complete_authorize; @@ -72,11 +74,11 @@ pub use self::payment_update_intent::PaymentUpdateIntent; #[cfg(feature = "v1")] pub use self::{ payment_approve::PaymentApprove, payment_cancel::PaymentCancel, - payment_capture::PaymentCapture, payment_confirm::PaymentConfirm, - payment_create::PaymentCreate, payment_post_session_tokens::PaymentPostSessionTokens, - payment_reject::PaymentReject, payment_session::PaymentSession, payment_start::PaymentStart, - payment_status::PaymentStatus, payment_update::PaymentUpdate, - payment_update_metadata::PaymentUpdateMetadata, + payment_cancel_post_capture::PaymentCancelPostCapture, payment_capture::PaymentCapture, + payment_confirm::PaymentConfirm, payment_create::PaymentCreate, + 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_incremental_authorization::PaymentIncrementalAuthorization, tax_calculation::PaymentSessionUpdate, }; diff --git a/crates/router/src/core/payments/operations/payment_attempt_record.rs b/crates/router/src/core/payments/operations/payment_attempt_record.rs index 3c5259b6bf1..f0b548891db 100644 --- a/crates/router/src/core/payments/operations/payment_attempt_record.rs +++ b/crates/router/src/core/payments/operations/payment_attempt_record.rs @@ -80,6 +80,7 @@ impl ValidateStatusForOperation for PaymentAttemptRecord { | common_enums::IntentStatus::Failed => Ok(()), common_enums::IntentStatus::Succeeded | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Processing | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::RequiresCustomerAction diff --git a/crates/router/src/core/payments/operations/payment_cancel_post_capture.rs b/crates/router/src/core/payments/operations/payment_cancel_post_capture.rs new file mode 100644 index 00000000000..66b8f0fc479 --- /dev/null +++ b/crates/router/src/core/payments/operations/payment_cancel_post_capture.rs @@ -0,0 +1,323 @@ +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 = "cancel_post_capture")] +pub struct PaymentCancelPostCapture; + +type PaymentCancelPostCaptureOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsCancelPostCaptureRequest, PaymentData>; + +#[async_trait] +impl GetTracker, api::PaymentsCancelPostCaptureRequest> + for PaymentCancelPostCapture +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &api::PaymentIdType, + request: &api::PaymentsCancelPostCaptureRequest, + merchant_context: &domain::MerchantContext, + _auth_flow: services::AuthFlow, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult< + operations::GetTrackerResponse< + 'a, + F, + api::PaymentsCancelPostCaptureRequest, + 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::Succeeded, + enums::IntentStatus::PartiallyCaptured, + enums::IntentStatus::PartiallyCapturedAndCapturable, + ], + "cancel_post_capture", + )?; + + let mut 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)?; + + 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(); + + payment_attempt + .cancellation_reason + .clone_from(&request.cancellation_reason); + + 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, + }; + + 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 PaymentCancelPostCapture +{ + #[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< + ( + PaymentCancelPostCaptureOperation<'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<( + PaymentCancelPostCaptureOperation<'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::PaymentsCancelPostCaptureRequest, + _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::PaymentsCancelPostCaptureRequest> + for PaymentCancelPostCapture +{ + #[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<(PaymentCancelPostCaptureOperation<'b, F>, PaymentData)> + where + F: 'b + Send, + { + Ok((Box::new(self), payment_data)) + } +} + +impl + ValidateRequest> + for PaymentCancelPostCapture +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + request: &api::PaymentsCancelPostCaptureRequest, + merchant_context: &'a domain::MerchantContext, + ) -> RouterResult<( + PaymentCancelPostCaptureOperation<'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/operations/payment_capture_v2.rs b/crates/router/src/core/payments/operations/payment_capture_v2.rs index 13100e7762f..9c2e766e558 100644 --- a/crates/router/src/core/payments/operations/payment_capture_v2.rs +++ b/crates/router/src/core/payments/operations/payment_capture_v2.rs @@ -38,6 +38,7 @@ impl ValidateStatusForOperation for PaymentsCapture { | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Processing | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index a3b81afb4f8..73aa834613d 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -47,6 +47,7 @@ impl ValidateStatusForOperation for PaymentIntentConfirm { | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Processing | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction diff --git a/crates/router/src/core/payments/operations/payment_get.rs b/crates/router/src/core/payments/operations/payment_get.rs index 621a6e71a70..3f4fb5ee6a3 100644 --- a/crates/router/src/core/payments/operations/payment_get.rs +++ b/crates/router/src/core/payments/operations/payment_get.rs @@ -42,6 +42,7 @@ impl ValidateStatusForOperation for PaymentGet { | common_enums::IntentStatus::PartiallyCapturedAndCapturable | common_enums::IntentStatus::PartiallyCaptured | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Expired => Ok(()), // These statuses are not valid for this operation diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 425173d4441..84deb90605a 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" + 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" )] pub struct PaymentResponse; @@ -1023,6 +1023,49 @@ impl PostUpdateTracker, types::PaymentsCancelData> f } } +#[cfg(feature = "v1")] +#[async_trait] +impl PostUpdateTracker, types::PaymentsCancelPostCaptureData> + for PaymentResponse +{ + async fn update_tracker<'b>( + &'b self, + db: &'b SessionState, + mut payment_data: PaymentData, + router_data: types::RouterData< + F, + types::PaymentsCancelPostCaptureData, + 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> @@ -2559,6 +2602,7 @@ impl PostUpdateTracker, types::PaymentsAuthor | common_enums::AttemptStatus::RouterDeclined | common_enums::AttemptStatus::AuthorizationFailed | common_enums::AttemptStatus::Voided + | common_enums::AttemptStatus::VoidedPostCharge | common_enums::AttemptStatus::VoidInitiated | common_enums::AttemptStatus::CaptureFailed | common_enums::AttemptStatus::VoidFailed diff --git a/crates/router/src/core/payments/operations/payment_session_intent.rs b/crates/router/src/core/payments/operations/payment_session_intent.rs index d713c5175b2..da6a8b9e049 100644 --- a/crates/router/src/core/payments/operations/payment_session_intent.rs +++ b/crates/router/src/core/payments/operations/payment_session_intent.rs @@ -33,6 +33,7 @@ impl ValidateStatusForOperation for PaymentSessionIntent { match intent_status { common_enums::IntentStatus::RequiresPaymentMethod => Ok(()), common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Processing | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction diff --git a/crates/router/src/core/payments/operations/payment_update_intent.rs b/crates/router/src/core/payments/operations/payment_update_intent.rs index 6e500e3eb0b..b8f19580f12 100644 --- a/crates/router/src/core/payments/operations/payment_update_intent.rs +++ b/crates/router/src/core/payments/operations/payment_update_intent.rs @@ -53,6 +53,7 @@ impl ValidateStatusForOperation for PaymentUpdateIntent { | common_enums::IntentStatus::Conflicted => Ok(()), common_enums::IntentStatus::Succeeded | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Processing | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index 3994fd8ef05..9170b36462a 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -49,6 +49,7 @@ impl ValidateStatusForOperation for PaymentProxyIntent { common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Succeeded | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction | common_enums::IntentStatus::RequiresCapture diff --git a/crates/router/src/core/payments/payment_methods.rs b/crates/router/src/core/payments/payment_methods.rs index 9bf9d3cc921..01beb8df7a6 100644 --- a/crates/router/src/core/payments/payment_methods.rs +++ b/crates/router/src/core/payments/payment_methods.rs @@ -364,6 +364,7 @@ fn validate_payment_status_for_payment_method_list( | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Processing | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index b435bd191ef..0ce5bd54674 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -782,6 +782,7 @@ impl | storage_enums::AttemptStatus::Authorizing | storage_enums::AttemptStatus::CodInitiated | storage_enums::AttemptStatus::Voided + | storage_enums::AttemptStatus::VoidedPostCharge | storage_enums::AttemptStatus::VoidInitiated | storage_enums::AttemptStatus::CaptureInitiated | storage_enums::AttemptStatus::RouterDeclined diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index dd9b41ee9d2..c0d1504e12e 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -4160,6 +4160,42 @@ impl TryFrom> for types::PaymentsCancelDa } } +#[cfg(feature = "v2")] +impl TryFrom> for types::PaymentsCancelPostCaptureData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + todo!() + } +} + +#[cfg(feature = "v1")] +impl TryFrom> for types::PaymentsCancelPostCaptureData { + 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: Some(amount), + currency: Some(payment_data.currency), + connector_transaction_id: connector + .connector + .connector_transaction_id(&payment_data.payment_attempt)? + .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, + cancellation_reason: payment_data.payment_attempt.cancellation_reason, + connector_meta: payment_data.payment_attempt.connector_metadata, + }) + } +} + impl TryFrom> for types::PaymentsApproveData { type Error = error_stack::Report; diff --git a/crates/router/src/core/revenue_recovery/transformers.rs b/crates/router/src/core/revenue_recovery/transformers.rs index 76f987673da..7faccbd0d7c 100644 --- a/crates/router/src/core/revenue_recovery/transformers.rs +++ b/crates/router/src/core/revenue_recovery/transformers.rs @@ -28,6 +28,7 @@ impl ForeignFrom for RevenueRecoveryPaymentsAttemptStatus { | AttemptStatus::Failure => Self::Failed, AttemptStatus::Voided + | AttemptStatus::VoidedPostCharge | AttemptStatus::ConfirmationAwaited | AttemptStatus::PartialCharged | AttemptStatus::PartialChargedAndChargeable diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 5d5a16912e2..b43f0138f39 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -1705,6 +1705,7 @@ fn get_desired_payment_status_for_dynamic_routing_metrics( | common_enums::AttemptStatus::Authorizing | common_enums::AttemptStatus::CodInitiated | common_enums::AttemptStatus::Voided + | common_enums::AttemptStatus::VoidedPostCharge | common_enums::AttemptStatus::VoidInitiated | common_enums::AttemptStatus::CaptureInitiated | common_enums::AttemptStatus::VoidFailed @@ -1736,6 +1737,7 @@ impl ForeignFrom for open_router::TxnStatus { common_enums::AttemptStatus::Voided | common_enums::AttemptStatus::Expired => { Self::Voided } + common_enums::AttemptStatus::VoidedPostCharge => Self::VoidedPostCharge, common_enums::AttemptStatus::VoidInitiated => Self::VoidInitiated, common_enums::AttemptStatus::CaptureInitiated => Self::CaptureInitiated, common_enums::AttemptStatus::CaptureFailed => Self::CaptureFailed, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 9584a9af160..0fbb177120b 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -805,6 +805,9 @@ impl Payments { .service( web::resource("/{payment_id}/cancel").route(web::post().to(payments::payments_cancel)), ) + .service( + web::resource("/{payment_id}/cancel_post_capture").route(web::post().to(payments::payments_cancel_post_capture)), + ) .service( web::resource("/{payment_id}/capture").route(web::post().to(payments::payments_capture)), ) diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 58865de64af..8defd917053 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -142,6 +142,7 @@ impl From for ApiIdentifier { | Flow::PaymentsConfirm | Flow::PaymentsCapture | Flow::PaymentsCancel + | Flow::PaymentsCancelPostCapture | Flow::PaymentsApprove | Flow::PaymentsReject | Flow::PaymentsSessionToken diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 74e6c580ff4..8687a5f9d06 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -1491,6 +1491,60 @@ pub async fn payments_cancel( .await } +#[cfg(feature = "v1")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsCancelPostCapture, payment_id))] +pub async fn payments_cancel_post_capture( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + let flow = Flow::PaymentsCancelPostCapture; + let mut payload = json_payload.into_inner(); + let payment_id = path.into_inner(); + + tracing::Span::current().record("payment_id", payment_id.get_string_repr()); + + payload.payment_id = 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::PostCaptureVoid, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( + state, + req_state, + merchant_context, + auth.profile_id, + payments::PaymentCancelPostCapture, + 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 +} + #[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] #[cfg(all(feature = "olap", feature = "v1"))] pub async fn payments_list( @@ -2507,6 +2561,23 @@ impl GetLockingInput for payment_types::PaymentsCancelRequest { } } +#[cfg(feature = "v1")] +impl GetLockingInput for payment_types::PaymentsCancelPostCaptureRequest { + 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 780093fd012..85a159f55f6 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1147,6 +1147,7 @@ impl Authenticate for api_models::payments::PaymentsRetrieveRequest { } } 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::PaymentsStartRequest {} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 3a51c628b82..579e8d91722 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -38,8 +38,8 @@ use hyperswitch_domain_models::router_flow_types::{ payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, - InitPayment, PSync, PostProcessing, PostSessionTokens, PreProcessing, Reject, - SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, + InitPayment, PSync, PostCaptureVoid, PostProcessing, PostSessionTokens, PreProcessing, + Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -72,10 +72,10 @@ pub use hyperswitch_domain_models::{ CompleteAuthorizeRedirectResponse, ConnectorCustomerData, CreateOrderRequestData, DefendDisputeRequestData, DestinationChargeRefund, DirectChargeRefund, MandateRevokeRequestData, MultipleCaptureRequestData, PaymentMethodTokenizationData, - PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, + PaymentsCancelPostCaptureData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, + PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, + PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, ResponseId, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, SplitRefundsRequest, SubmitEvidenceRequestData, SyncRequestType, UploadFileRequestData, VaultRequestData, @@ -101,12 +101,12 @@ pub use hyperswitch_domain_models::{ pub use hyperswitch_interfaces::types::{ AcceptDisputeType, ConnectorCustomerType, DefendDisputeType, IncrementalAuthorizationType, MandateRevokeType, PaymentsAuthorizeType, PaymentsBalanceType, PaymentsCaptureType, - PaymentsCompleteAuthorizeType, PaymentsInitType, PaymentsPostProcessingType, - PaymentsPostSessionTokensType, PaymentsPreAuthorizeType, PaymentsPreProcessingType, - PaymentsSessionType, PaymentsSyncType, PaymentsUpdateMetadataType, PaymentsVoidType, - RefreshTokenType, RefundExecuteType, RefundSyncType, Response, RetrieveFileType, - SdkSessionUpdateType, SetupMandateType, SubmitEvidenceType, TokenizationType, UploadFileType, - VerifyWebhookSourceType, + PaymentsCompleteAuthorizeType, PaymentsInitType, PaymentsPostCaptureVoidType, + PaymentsPostProcessingType, PaymentsPostSessionTokensType, PaymentsPreAuthorizeType, + PaymentsPreProcessingType, PaymentsSessionType, PaymentsSyncType, PaymentsUpdateMetadataType, + PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType, Response, + RetrieveFileType, SdkSessionUpdateType, SetupMandateType, SubmitEvidenceType, TokenizationType, + UploadFileType, VerifyWebhookSourceType, }; #[cfg(feature = "payouts")] pub use hyperswitch_interfaces::types::{ @@ -164,6 +164,8 @@ pub type PaymentsUpdateMetadataRouterData = RouterData; pub type PaymentsCancelRouterData = RouterData; +pub type PaymentsCancelPostCaptureRouterData = + RouterData; pub type PaymentsRejectRouterData = RouterData; pub type PaymentsApproveRouterData = RouterData; pub type PaymentsSessionRouterData = RouterData; @@ -184,6 +186,8 @@ pub type PaymentsResponseRouterData = ResponseRouterData; pub type PaymentsCancelResponseRouterData = ResponseRouterData; +pub type PaymentsCancelPostCaptureResponseRouterData = + ResponseRouterData; pub type PaymentsBalanceResponseRouterData = ResponseRouterData; pub type PaymentsSyncResponseRouterData = @@ -306,6 +310,7 @@ impl Capturable for PaymentsAuthorizeData { | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Expired => Some(0), common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::PartiallyCaptured | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction @@ -348,6 +353,7 @@ impl Capturable for PaymentsCaptureData { | common_enums::IntentStatus::Expired => Some(0), common_enums::IntentStatus::Processing | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::Failed | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction @@ -393,6 +399,7 @@ impl Capturable for CompleteAuthorizeData { | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Expired => Some(0), common_enums::IntentStatus::Cancelled | common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::CancelledPostCapture | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction | common_enums::IntentStatus::RequiresPaymentMethod @@ -437,6 +444,45 @@ impl Capturable for PaymentsCancelData { let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); match intent_status { common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::CancelledPostCapture + | common_enums::IntentStatus::Processing + | 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 => None, + } + } +} +impl Capturable for PaymentsCancelPostCaptureData { + fn get_captured_amount(&self, 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, + 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::Processing | common_enums::IntentStatus::PartiallyCaptured | common_enums::IntentStatus::Conflicted diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index e28ac53ad32..f04dc1a6fd8 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -19,8 +19,8 @@ pub use api_models::{ MandateValidationFields, NextActionType, OpenBankingSessionToken, PayLaterData, PaymentIdType, PaymentListConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentMethodData, PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, - PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, - PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, + PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelPostCaptureRequest, + PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, PaymentsPostSessionTokensRequest, @@ -37,24 +37,25 @@ use error_stack::ResultExt; pub use hyperswitch_domain_models::router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, InitPayment, PSync, - PaymentCreateIntent, PaymentGetIntent, PaymentMethodToken, PaymentUpdateIntent, PostProcessing, - PostSessionTokens, PreProcessing, RecordAttempt, Reject, SdkSessionUpdate, Session, - SetupMandate, UpdateMetadata, Void, + 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, PaymentAuthorizeSessionToken, PaymentCapture, PaymentIncrementalAuthorization, - PaymentPostSessionTokens, PaymentReject, PaymentSession, PaymentSessionUpdate, PaymentSync, - PaymentToken, PaymentUpdateMetadata, PaymentVoid, PaymentsCompleteAuthorize, - PaymentsCreateOrder, PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, + PaymentPostCaptureVoid, PaymentPostSessionTokens, PaymentReject, PaymentSession, + PaymentSessionUpdate, PaymentSync, PaymentToken, PaymentUpdateMetadata, PaymentVoid, + PaymentsCompleteAuthorize, PaymentsCreateOrder, PaymentsPostProcessing, PaymentsPreProcessing, + TaxCalculation, }; pub use super::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, - PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, - PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, - PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + 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 78e3ea7a706..ad0a615a609 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, - PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, - PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, - PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + PaymentPostCaptureVoidV2, PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, + PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, + PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, }; diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 02056774729..e7978e1dcdb 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -161,6 +161,7 @@ impl ForeignTryFrom for storage_enums::CaptureStat | storage_enums::AttemptStatus::Authorizing | storage_enums::AttemptStatus::CodInitiated | storage_enums::AttemptStatus::Voided + | storage_enums::AttemptStatus::VoidedPostCharge | storage_enums::AttemptStatus::VoidInitiated | storage_enums::AttemptStatus::VoidFailed | storage_enums::AttemptStatus::AutoRefunded diff --git a/crates/router_derive/src/macros/operation.rs b/crates/router_derive/src/macros/operation.rs index 103f83076bf..2e6dabe0b29 100644 --- a/crates/router_derive/src/macros/operation.rs +++ b/crates/router_derive/src/macros/operation.rs @@ -19,6 +19,8 @@ pub enum Derives { AuthorizeData, SyncData, CancelData, + CancelPostCapture, + CancelPostCaptureData, CaptureData, CompleteAuthorizeData, RejectData, @@ -129,6 +131,12 @@ impl Conversion { Derives::UpdateMetadataData => { syn::Ident::new("PaymentsUpdateMetadataData", Span::call_site()) } + Derives::CancelPostCapture => { + syn::Ident::new("PaymentsCancelPostCaptureRequest", Span::call_site()) + } + Derives::CancelPostCaptureData => { + syn::Ident::new("PaymentsCancelPostCaptureData", Span::call_site()) + } } } @@ -452,6 +460,7 @@ pub fn operation_derive_inner(input: DeriveInput) -> syn::Result syn::Result